Compare commits

..
74 Commits
Author SHA1 Message Date
JingMatrix b02917aeda Release v1.0 2025-11-02 18:42:17 +01:00
JingMatrix 54f80717dc Improve gradle task configuration
1. Declare explicit dependency to allow incremental build for installation tasks
2. Eliminate lint errors
2025-11-02 18:40:45 +01:00
JingMatrix ed9164d9d8 Rename TrickyStoreOSS to TEESimulator (#3)
We should keep the java class name to facilitate the process of tracking back commit history.
2025-11-02 15:56:33 +01:00
JingMatrix c5c81b80c3 Add ktfmt-gradle as formatter
Run `gradle format` to format all kotlin source code
2025-11-02 11:16:41 +01:00
JingMatrixandGitHub 093c1bbc13 Implement multi-keybox support (#1)
This commit adds support for using different `keybox.xml` files for different applications. This allows for greater flexibility, enabling the use of distinct cryptographic identities for specific groups of apps.

The `target.txt` configuration now supports a new syntax. A line with a filename in brackets, like `[demo_keybox.xml]`, sets the active keybox for all subsequent packages in the file.

The implementation refactors the configuration and keybox loading logic:

- PkgConfig now parses the new syntax and maps packages to their designated keybox files.

- KeyBoxUtils is updated to dynamically load and cache multiple keybox files on demand, instead of relying on a single global state.

- Certificate generation and hacking functions now use this new configuration to select the correct keybox for each operation based on the application's UID.
2025-11-02 11:07:50 +01:00
JingMatrix ad81149700 Improve development experience
1. Add installation tasks
2. Export CMake symbols for clangd
2025-11-02 10:54:28 +01:00
JingMatrix 67d35caf3c Rename project to TEESimulator
Explain in README our new goal of bypass TrickyStore detection
2025-11-02 10:52:09 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
258258a846 build(deps): bump agp from 8.12.2 to 8.13.0 (#32)
Bumps `agp` from 8.12.2 to 8.13.0.

Updates `com.android.application` from 8.12.2 to 8.13.0

Updates `com.android.library` from 8.12.2 to 8.13.0

---
updated-dependencies:
- dependency-name: com.android.application
  dependency-version: 8.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.android.library
  dependency-version: 8.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 13:34:00 +05:30
Dakkshesh ff980f59fc interceptors: keystore2: Handle missing keys error properly
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-09-04 12:35:17 +05:30
Dakkshesh 7d371dd766 Revert "java: Implement keystore clearing for target packages during initialization"
This reverts commit c285992226.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-09-03 10:39:05 +05:30
Dakkshesh 77323fad91 Revert "java: config: Limit clear keystore workaround for few affected apps"
This reverts commit 2185237267.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-09-03 10:37:53 +05:30
Dakkshesh 2185237267 java: config: Limit clear keystore workaround for few affected apps
- To avoid side affects caused to unwanted apps that are added to target.txt

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-09-03 10:03:32 +05:30
Dakkshesh 9701bd3978 java: config: Refactor waitAndGetSystemService()
- Make use of waitForService() for >= Android 11.
- Reserve manual polling for Android 10 due to lack of waitForService().
- Rework on logs to be less cluttering.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-09-01 15:02:48 +05:30
Furkan KarcıoğluandGitHub 2fad7ff251 java: make getPM() wait for package manager service initialisation (#30) 2025-09-01 00:25:45 +05:30
Dakkshesh c285992226 java: Implement keystore clearing for target packages during initialization
- Currently using keystore2 API.
- TODO: Add support for keystore1.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-31 20:48:52 +05:30
Dakkshesh 1e53ffeea0 java: config: guard linkToDeath() in getPm() against null binder to prevent NPE
fixes: https://github.com/beakthoven/TrickyStoreOSS/commit/23774e64a94c5cab08daa3ba2cec7a98db850cbd

logs:
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS: fatal: failed to get packages
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS: java.lang.NullPointerException: Attempt to invoke interface method 'void android.os.IBinder.linkToDeath(android.os.IBinder$DeathRecipient, int)' on a null object reference
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at qe.a(Unknown Source:13)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at qe.c(Unknown Source:1)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor.onPreTransact(Unknown Source:107)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at io.github.beakthoven.TrickyStoreOSS.interceptors.BinderInterceptor.handlePreTransact(Unknown Source:50)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at io.github.beakthoven.TrickyStoreOSS.interceptors.BinderInterceptor.onTransact(Unknown Source:21)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.os.Binder.execTransactInternal(Binder.java:1392)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.os.Binder.execTransact(Binder.java:1299)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.os.BinderProxy.transactNative(Native Method)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.os.BinderProxy.transact(BinderProxy.java:685)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.system.keystore2.IKeystoreSecurityLevel$Stub$Proxy.generateKey(IKeystoreSecurityLevel.java:307)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.security.KeyStoreSecurityLevel.lambda$generateKey$0(KeyStoreSecurityLevel.java:145)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.security.KeyStoreSecurityLevel.$r8$lambda$XUH2FEW76sslYQDkYWi9Fg4yVEI(Unknown Source:0)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.security.KeyStoreSecurityLevel$$ExternalSyntheticLambda1.execute(Unknown Source:12)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.security.KeyStoreSecurityLevel.handleExceptions(KeyStoreSecurityLevel.java:55)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.security.KeyStoreSecurityLevel.generateKey(KeyStoreSecurityLevel.java:145)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at android.security.keystore2.AndroidKeyStoreKeyPairGeneratorSpi.generateKeyPair(AndroidKeyStoreKeyPairGeneratorSpi.java:651)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at java.security.KeyPairGenerator$Delegate.generateKeyPair(KeyPairGenerator.java:750)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at W.a(Unknown Source:497)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at vi.a(Unknown Source:20)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at io.github.beakthoven.TrickyStoreOSS.MainKt.a(Unknown Source:175)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at io.github.beakthoven.TrickyStoreOSS.MainKt.main(Unknown Source:15)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
08-30 15:46:05.226  1811  1811 E TrickyStoreOSS:        at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:379)

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-30 19:12:14 +05:30
Dakkshesh 808c3a5ff9 app: java: Restructure and tidy up the code
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-30 16:02:34 +05:30
JingMatrixandGitHub 23774e64a9 app: config: Use linkToDeath for PackageManager resilience (#29)
The previous implementation used `pingBinder()` to check the liveness of the PackageManager service on every call to `getPm()`. This polling approach introduces an unnecessary IPC round-trip overhead for every access.

This commit refactors the logic to use the canonical, event-driven pattern for handling remote service death by implementing `linkToDeath`.

A `DeathRecipient` is now registered with the binder upon the first connection. If the service process (`system_server`) dies for any reason, the `binderDied()` callback is automatically invoked by the system. This callback proactively clears the cached service instance, ensuring that the next call to `getPm()` will transparently re-establish a valid connection.
2025-08-29 21:56:33 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a064c137c3 build(deps): bump agp from 8.12.1 to 8.12.2 (#28)
Bumps `agp` from 8.12.1 to 8.12.2.

Updates `com.android.application` from 8.12.1 to 8.12.2

Updates `com.android.library` from 8.12.1 to 8.12.2

---
updated-dependencies:
- dependency-name: com.android.application
  dependency-version: 8.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.android.library
  dependency-version: 8.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 17:21:22 +05:30
rhunkandGitHub bcb4531904 app: Fix DeadObjectException in IPackageManager access (#27) 2025-08-28 19:30:08 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a758694ceb build(deps): bump agp from 8.12.0 to 8.12.1 (#23)
Bumps `agp` from 8.12.0 to 8.12.1.

Updates `com.android.application` from 8.12.0 to 8.12.1

Updates `com.android.library` from 8.12.0 to 8.12.1

---
updated-dependencies:
- dependency-name: com.android.application
  dependency-version: 8.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.android.library
  dependency-version: 8.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-22 09:17:51 +05:30
Dakkshesh d762663192 java: Overhaul detection of attestation properties
- Reuse and extend the attestation we perform for detection of functional TEE.
- Extract properties like boot hash, os version, attest version, keymaster version from the generated certificate.
- Use previous methods as fallback for devices without functional TEE.
- Set ro.boot.vbmeta.digest during initialization with the boot hash value.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-19 19:33:27 +05:30
Dakkshesh 088e9ff1b0 java: Stop using dev.rikka.hidden.stub
- We dont need the whole dev.rikka.hidden.stub library so just remove it and simplify dependencies.
- We already have our own stub package so we can just include the missing stubs that we want directly in it.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-17 23:15:44 +05:30
Dakkshesh af2707765a workflows: build: Remove redundant setup/configuration
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-17 21:29:20 +05:30
Dakkshesh efb704500e .idea: Refresh
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-17 21:29:03 +05:30
Nicholas BissellandGitHub 095b32554b java: CertHack: Honour StrongBox security level when requested in generate mode (#20) 2025-08-17 12:29:09 +05:30
MONAandGitHub 7410315701 Add more package names to target.txt (#21)
* Add more root detection and security analysis app package names to target.txt
* Revised commit: kept only apps that check bootloader status
2025-08-17 11:46:20 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8b19dc5376 build(deps): bump org.jetbrains.kotlin.android from 2.2.0 to 2.2.10 (#18)
Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 2.2.0 to 2.2.10.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.0...v2.2.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin.android
  dependency-version: 2.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-14 17:37:43 +05:30
Nicholas BissellandGitHub a41c49dcbf Correct package paths for keystore2 API stub (#16) 2025-08-14 13:16:51 +05:30
Dakkshesh 6517fd2569 java: CertHacker: Pass imei2 field correctly
- It was being passed with the wrong tag ID
- Only pass it if attestVersion is >= 300

ref: https://source.android.com/docs/security/features/keystore/attestation#authorizationlist-fields

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-13 12:26:25 +05:30
Dakkshesh 10c1a41981 java: CertHacker: Handle attest and keymaster version gracefully
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-13 12:06:24 +05:30
DakksheshandGitHub e768569ed6 README: Add vvb2060/KeyAttestation to acknowledgement 2025-08-11 22:05:42 +05:30
DakksheshandGitHub c55df1743b README: Add android requirement 2025-08-11 22:03:56 +05:30
Dakkshesh 5d2ba396e1 Revert "module: Set minimum sdk to 31"
- Injection failures have been fixed on android 10/11.

This reverts commit de71e45bf3.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-11 20:34:08 +05:30
Dakkshesh a33069b481 java: CertHack: Dont append moduleHash for < Android 12
- To avoid "Unknown tag 724" error on older platfoms like Android 10/11
- Conditionally downgrade keymaster versions to 3.0 on these platforms

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-11 20:33:37 +05:30
Dakkshesh eaa017be86 cpp: RefBase: Dont use incWeakRequireWeak()
- Fixes the following on older android 10/11 platforms:
08-11 14:53:05.143  1388  1388 I TrickyStoreOSS: TrickyStore injector starting...
08-11 14:53:05.144  1388  1388 I TrickyStoreOSS: Starting injection of /data/adb/modules/tricky_store/libTrickyStoreOSS.so (entry: entry) into process 1024
08-11 14:53:05.151   587   587 I Zygote  : Preloading shared libraries...
08-11 14:53:05.155  1026  1053 I ServiceManager: Waiting for service 'package_native' on '/dev/binder'...
08-11 14:53:05.155  1388  1388 W TrickyStoreOSS: Primary dlopen failed for library: /data/adb/modules/tricky_store/libTrickyStoreOSS.so, dlerror: dlopen failed: cannot locate symbol "_ZN7android7RefBase12weakref_type18incWeakRequireWeakEPKv" referenced by "/data/adb/modules/tricky_store/libTrickyStoreOSS.so"..., trying fallback
08-11 14:53:05.155  1388  1388 I TrickyStoreOSS: Using fallback dlopen method for compatibility
08-11 14:53:05.158  1388  1388 E TrickyStoreOSS: Fallback dlopen failed for library: /data/adb/modules/tricky_store/libTrickyStoreOSS.so, dlerror: dlopen failed: cannot locate symbol "_ZN7android7RefBase12weakref_type18incWeakRequireWeakEPKv" referenced by "/data/adb/modules/tricky_store/libTrickyStoreOSS.so"...
08-11 14:53:05.158  1388  1388 E TrickyStoreOSS: Failed to load library in remote process
08-11 14:53:05.158  1388  1388 E TrickyStoreOSS: Injection failed

- Also improve error handling in injection process

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-11 20:33:37 +05:30
Dakkshesh 60bf475d48 app: Bump version to 2.1.0
Signed-off-by: Dakkshesh <dakkshesh5@gmail.com>
2025-08-11 16:21:59 +05:30
Dakkshesh de71e45bf3 module: Set minimum sdk to 31
- There are some issues with Android 10/11 that causes injection failures.
- Raise the compatiblity requirement until they are fixed.

Signed-off-by: Dakkshesh <dakkshesh5@gmail.com>
2025-08-11 16:20:15 +05:30
Dakkshesh f723e53e2b java: CertHacker: Sanitize the XML data before passing to XML parser
- Remove whitespaces
- Remove Byte Order Mark (BOM)

Fixes #12

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-10 09:39:32 +05:30
Dakkshesh ef87d5dddf java: CertificateHacker: Handle getPackageInfo() correctly
Fixes #11

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-10 00:13:10 +05:30
Dakkshesh b31b023018 workflows: changelogs: Fix zip detection
Signed-off-by: Dakkshesh <dakkshesh5@gmail.com>
2025-08-08 15:41:21 +05:30
Dakkshesh adac71b5bc workflows: changelogs: Update repo URL
Signed-off-by: Dakkshesh <dakkshesh5@gmail.com>
2025-08-08 14:41:29 +05:30
Dakkshesh a17b276875 workflows: Add changelog generator
Signed-off-by: Dakkshesh <dakkshesh5@gmail.com>
2025-08-08 14:39:10 +05:30
Dakkshesh 67f7fa061f java: CertificiateHacker: Refactor and tidy up a bit
- Removed unused boilerplate from initial bring up
- Inlined some functions which were fragmented due to some initial plans that were dropped
- Moved appropriate functions to CertificateUtils
- Tidy up some function and variable names

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 22:47:12 +05:30
Dakkshesh 64a998000a cpp: Switch to C++23 standard
- Our codebase is written well to support this.
- LSPlt will continue to use c++20 via its cmake logic.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 16:49:42 +05:30
Dakkshesh 52e8056b90 cpp: Move LSPlt configuration to gradle script
Otherwise for some reason it fails to set the vars in CI builds.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 16:24:42 +05:30
Dakkshesh 6b73ee7d3f workflows: Remove txt from ignore list
My bad, i forgot CMakeLists.txt exists.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 16:09:05 +05:30
Dakkshesh f2c0ed7beb cpp: LSPlt: Disable building shared lib
We dont need it. This also fixes some build errors when `DEBUG_SYMBOLS_PATH` is undefined.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 16:03:09 +05:30
Dakkshesh 9ed495df6d cpp: use LSPlt's own CMake build logic
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 15:41:38 +05:30
Dakkshesh 289f10cb47 cpp: Update LSPlt submodule
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 15:41:07 +05:30
Dakkshesh 54c798b380 cpp: stub: Format using clang-format
Somehow this was missed out during initial formatting.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 11:41:27 +05:30
Chris RenshawandGitHub 0f40a2b76b Update target.txt with other common checkers (#9) 2025-08-07 11:37:21 +05:30
Dakkshesh bba2281241 config: remove hardcoded generatePackages entries
- This is a leftover from initial bring up when target.txt parsing wasn't implemented.
- Not needed now because we have auto mode.

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 11:33:37 +05:30
Dakkshesh 52847b8870 github: Add build workflow
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-07 00:05:54 +05:30
Dakkshesh 0010515c0b module: Fix up update json link
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 21:08:56 +05:30
Dakkshesh 7f57e82ff9 README: Fix up acknowledgement link
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 21:06:15 +05:30
Dakkshesh 435a79ed64 src: Correct log tag
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 21:06:03 +05:30
Dakkshesh 7ac40416f8 gradle: Update to 9.0.0
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 21:01:53 +05:30
Dakkshesh eec1d8c390 Setup dependabot
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 21:01:53 +05:30
Chris RenshawandDakkshesh 0ae8be899d Sync with official service.sh but also tidy (#8)
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 21:01:52 +05:30
Dakkshesh 48b94e8670 gitignore: Add .idea/codeStyles & sort
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 21:01:46 +05:30
Dakkshesh c5b82ef67d src: dos2unix
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 21:01:18 +05:30
Dakkshesh fa2fa1c7d5 Add README.md
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 19:46:58 +05:30
DakksheshandChris Renshaw f9c912ad92 module: Add AOSP software keybox to pass MEETS_DEVICE_INTEGRITY by default
Co-authored-by: Chris Renshaw <osm0sis@outlook.com>
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 19:46:52 +05:30
Dakkshesh e46820275f src: Add module files
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 19:46:42 +05:30
Dakkshesh 87bb7b2df1 cpp: external: Clean AOSP headers and resolve missing dependencies
- To solve some dependency issues by removing redundant code.
- Added missing headers

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 17:50:34 +05:30
Dakkshesh 4898f0efc2 src: cpp: Import binder uapi header from linux kernel
tag: v6.16

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 17:49:37 +05:30
Dakkshesh eadeb3ed00 src: cpp: Switch to JingMatrix's LSPlt fork
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 17:49:16 +05:30
Dakkshesh 2a0f54e336 src: cpp: Pull required headers from AOSP
tag: android-16.0.0_r1

Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-06 17:48:38 +05:30
Dakkshesh 8ed149154f src: cpp: Add LSPlt submodule
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-04 14:11:24 +05:30
Dakkshesh 205f939deb build: Update relavant tools
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-04 14:07:41 +05:30
Dakkshesh 9549296d69 src: Initialize gplv3 license
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-04 14:01:31 +05:30
Dakkshesh 30e25f461a src: Clean up
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-04 13:37:26 +05:30
Dakkshesh 9eb151eb07 Initialize a native project
Signed-off-by: Dakkshesh <beakthoven@gmail.com>
2025-08-04 09:38:02 +05:30
197 changed files with 5553 additions and 17977 deletions
+17
View File
@@ -0,0 +1,17 @@
BasedOnStyle: LLVM
Language: Cpp
Standard: c++20
ColumnLimit: 135
AlignEscapedNewlines: Left
AllowShortFunctionsOnASingleLine: Empty
AllowShortLambdasOnASingleLine: Empty
AlwaysBreakTemplateDeclarations: true
IndentPPDirectives: AfterHash
AccessModifierOffset: -4
IndentWidth: 4
UseTab: Never
+68 -96
View File
@@ -3,10 +3,16 @@ name: Build
on: on:
push: push:
branches: [ "main" ] branches: [ "main" ]
paths-ignore: [ '**.md' ] paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/**'
pull_request: pull_request:
branches: [ "main" ] branches: [ "main" ]
paths-ignore: [ '**.md' ] paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/**'
workflow_dispatch: workflow_dispatch:
concurrency: concurrency:
@@ -16,9 +22,18 @@ concurrency:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
id-token: write
attestations: write
contents: read
outputs:
releaseName: ${{ steps.prepareArtifact.outputs.releaseName }}
debugName: ${{ steps.prepareArtifact.outputs.debugName }}
steps: steps:
- uses: actions/checkout@v4 - name: Check out
uses: actions/checkout@v4
with: with:
submodules: "recursive" submodules: "recursive"
fetch-depth: 0 fetch-depth: 0
@@ -30,25 +45,6 @@ jobs:
java-version: 21 java-version: 21
cache: 'gradle' cache: 'gradle'
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
- name: Cache Rust artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
~/.cargo/bin/cargo-ndk
native-certgen/target
key: rust-${{ runner.os }}-${{ hashFiles('native-certgen/Cargo.lock') }}
restore-keys: rust-${{ runner.os }}-
- name: Install cargo-ndk
run: command -v cargo-ndk || cargo install cargo-ndk
- name: Set up ccache - name: Set up ccache
uses: hendrikmuhs/ccache-action@v1.2 uses: hendrikmuhs/ccache-action@v1.2
with: with:
@@ -64,96 +60,72 @@ jobs:
- name: Build with Gradle - name: Build with Gradle
run: | run: |
chmod +x ./gradlew chmod +x ./gradlew
./gradlew zipRelease zipDebug -Porg.gradle.parallel=true -Porg.gradle.vfs.watch=true -Dorg.gradle.jvmargs=-Xmx2048m ./gradlew --parallel zipRelease zipDebug --stacktrace
- name: Read version - name: Prepare artifact
id: ver if: success()
run: | id: prepareArtifact
ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/')
echo "version=${ver}" >> "$GITHUB_OUTPUT"
- name: Rename ZIPs for release
run: | run: |
set -e
RELEASE_FILE=$(find out -name "*Release*.zip" | head -1) RELEASE_FILE=$(find out -name "*Release*.zip" | head -1)
DEBUG_FILE=$(find out -name "*Debug*.zip" | head -1) DEBUG_FILE=$(find out -name "*Debug*.zip" | head -1)
if [[ -z "$RELEASE_FILE" || -z "$DEBUG_FILE" ]]; then if [[ -z "$RELEASE_FILE" || -z "$DEBUG_FILE" ]]; then
echo "::error::Could not find release or debug ZIPs in out/" echo "Error: Could not find release or debug files in out/"
ls -la out/ || echo "out/ does not exist" echo "Contents of out/ directory:"
ls -la out/ || echo "out/ directory does not exist"
exit 1 exit 1
fi fi
# Extract names
RELEASE_NAME=$(basename "$RELEASE_FILE" .zip)
DEBUG_NAME=$(basename "$DEBUG_FILE" .zip)
echo "releaseName=$RELEASE_NAME" >> $GITHUB_OUTPUT
echo "debugName=$DEBUG_NAME" >> $GITHUB_OUTPUT
mv "$RELEASE_FILE" "out/TEESimulator-${VER}-Release.zip" mkdir -p module-release module-debug
mv "$DEBUG_FILE" "out/TEESimulator-${VER}-Debug.zip" unzip -q "$RELEASE_FILE" -d module-release
unzip -q "$DEBUG_FILE" -d module-debug
echo " Release: $RELEASE_NAME"
echo " Debug: $DEBUG_NAME"
echo "Release: TEESimulator-${VER}-Release.zip ($(du -h "out/TEESimulator-${VER}-Release.zip" | cut -f1))" - name: Upload release
echo "Debug: TEESimulator-${VER}-Debug.zip ($(du -h "out/TEESimulator-${VER}-Debug.zip" | cut -f1))" if: success()
env: id: release
VER: ${{ steps.ver.outputs.version }} uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v4
with: with:
name: TEESimulator-release-zip name: ${{ steps.prepareArtifact.outputs.releaseName }}
path: out/TEESimulator-*-Release.zip path: "./module-release/*"
retention-days: 30 retention-days: 30
compression-level: 0 compression-level: 6
- uses: actions/upload-artifact@v4 - name: Upload debug
if: success()
id: debug
uses: actions/upload-artifact@v4
with: with:
name: TEESimulator-debug-zip name: ${{ steps.prepareArtifact.outputs.debugName }}
path: out/TEESimulator-*-Debug.zip path: "./module-debug/*"
retention-days: 7 retention-days: 7
compression-level: 0 compression-level: 6
- uses: actions/upload-artifact@v4 - name: Upload release mappings
if: success()
uses: actions/upload-artifact@v4
with: with:
name: release-mappings name: release-mappings-${{ github.run_number }}
path: app/build/outputs/mapping/release path: "./app/build/outputs/mapping/release"
retention-days: 30 retention-days: 30
compression-level: 9 compression-level: 9
release: - name: Summary
needs: build if: always()
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Read version
id: ver
run: | run: |
ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/') echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "version=${ver}" >> "$GITHUB_OUTPUT" echo "- **Status**: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
echo "- **Gradle Tasks**: assembleRelease, assembleDebug" >> $GITHUB_STEP_SUMMARY
- uses: actions/download-artifact@v4 if [[ "${{ job.status }}" == "success" ]]; then
with: echo "- **Release Artifact**: ${{ steps.prepareArtifact.outputs.releaseName }}" >> $GITHUB_STEP_SUMMARY
name: TEESimulator-release-zip echo "- **Debug Artifact**: ${{ steps.prepareArtifact.outputs.debugName }}" >> $GITHUB_STEP_SUMMARY
path: zips fi
- uses: actions/download-artifact@v4
with:
name: TEESimulator-debug-zip
path: zips
- name: Extract changelog
run: |
ver="${VER#v}"
awk "/^## TEESimulator v${ver}/{flag=1; next} /^## TEESimulator v/{if(flag) exit} flag" module/changelog.md > /tmp/notes.md
cat /tmp/notes.md
env:
VER: ${{ steps.ver.outputs.version }}
- name: Create release
run: |
gh release delete "$VER" --yes 2>/dev/null || true
gh release create "$VER" \
--title "$VER" \
--latest \
--notes-file /tmp/notes.md \
"zips/TEESimulator-${VER}-Release.zip" \
"zips/TEESimulator-${VER}-Debug.zip"
env:
VER: ${{ steps.ver.outputs.version }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+72
View File
@@ -0,0 +1,72 @@
name: Publish Changelog
on:
release:
types: [published, edited]
jobs:
generate-files:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Clone main branch
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Calculate version code
id: vercode
run: |
echo "versionCode=$(git rev-list --count HEAD)" >> $GITHUB_OUTPUT
- name: Fetch release metadata
id: release
env:
ASSETS: ${{ toJson(github.event.release.assets) }}
run: |
echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "${{ github.event.release.body }}" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
URL=$(echo "$ASSETS" | jq -r '.[] | select(.name | endswith("Release.zip")) | .browser_download_url' | head -n1)
if [ -z "$URL" ]; then
echo "::error::Missing .zip asset in release uploads."
echo "$ASSETS" | jq -r '.[].name'
exit 1
fi
echo "zip_url=$URL" >> $GITHUB_OUTPUT
- name: Checkout changelog branch
uses: actions/checkout@v4
with:
ref: changelog
path: changelog-branch
- name: Write changelog and JSON
run: |
cd changelog-branch
echo "${{ steps.release.outputs.notes }}" > changelog.md
cat <<EOF > update.json
{
"version": "${{ steps.release.outputs.tag }}",
"versionCode": ${{ steps.vercode.outputs.versionCode }},
"zipUrl": "${{ steps.release.outputs.zip_url }}",
"changelog": "https://raw.githubusercontent.com/beakthoven/TrickyStoreOSS/changelog/changelog.md"
}
EOF
- name: Commit and push updates
run: |
cd changelog-branch
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add changelog.md update.json
git commit -m "Update metadata for ${{ steps.release.outputs.tag }}"
git push origin changelog -f
+19 -1
View File
@@ -1 +1,19 @@
out .cxx
.DS_Store
.externalNativeBuild
.gradle
*.iml
/.idea/assetWizardSettings.xml
/.idea/caches
/.idea/codeStyles/
/.idea/libraries
/.idea/modules.xml
/.idea/navEditor.xml
/.idea/workspace.xml
/build
/captures
/local.properties
module/classes.dex
module/lib/
module/service.apk
out/
+1 -1
View File
@@ -1,3 +1,3 @@
[submodule "app/src/main/cpp/external/LSPlt"] [submodule "app/src/main/cpp/external/LSPlt"]
path = app/src/main/cpp/external/LSPlt path = app/src/main/cpp/external/LSPlt
url = https://github.com/JingMatrix/LSPlt url = https://github.com/JingMatrix/LSPlt.git
+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
Generated
+1
View File
@@ -0,0 +1 @@
Tricky Store OSS
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="temurin-21" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/stub" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>
+9
View File
@@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>
Generated
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/app/src/main/cpp/external/LSPlt" vcs="Git" />
</component>
</project>
+1 -8
View File
@@ -1,13 +1,6 @@
This project is licensed under the GNU General Public License v3.0 This project is licensed under the GNU General Public License v3.0
(see LICENSE file). (see LICENSE file).
Source code based on:
--------------
https://github.com/5ec1cff/TrickyStore/commit/3a515c5fe1ce4c94d5424305afe2eaf4812a635d
https://github.com/beakthoven/TrickyStoreOSS/commit/8625be6ce55c3ef0f2219f301daec23202017bc0
Third-party components: Third-party components:
----------------------- -----------------------
@@ -30,4 +23,4 @@ Third-party components:
The file is licensed under: The file is licensed under:
GNU General Public License v2.0 WITH Linux-syscall-note exception. GNU General Public License v2.0 WITH Linux-syscall-note exception.
Information about the exception: Information about the exception:
https://spdx.org/licenses/Linux-syscall-note.html https://spdx.org/licenses/Linux-syscall-note.html
+42 -208
View File
@@ -1,143 +1,32 @@
<p align="center"> # TEESimulator A Full TEE Emulation Framework
<h1 align="center">🔐 TEESimulator</h1>
<p align="center"><b>Full TEE Emulation for Rooted Android</b></p>
<p align="center">Hardware attestation. Software keys. Zero detection.</p>
<p align="center">
<a href="https://github.com/Enginex0/TEESimulator/actions/workflows/build.yml"><img src="https://github.com/Enginex0/TEESimulator/actions/workflows/build.yml/badge.svg" alt="Build"></a>
<img src="https://img.shields.io/badge/version-v4.2-blue?style=for-the-badge" alt="v4.2">
<img src="https://img.shields.io/badge/Android-10%2B-green?style=for-the-badge&logo=android" alt="Android 10+">
<img src="https://img.shields.io/badge/Telegram-community-blue?style=for-the-badge&logo=telegram" alt="Telegram">
</p>
</p>
--- **TEESimulator** is a FOSS system module designed to create a complete, software-based simulation of a hardware-backed Trusted Execution Environment ([TEE](https://source.android.com/docs/security/features/trusty)) for [Key Attestation](https://developer.android.com/privacy-and-security/security-key-attestation).
> [!NOTE] The project's goal is to move beyond simple certificate patching and build a robust framework that can create and manage virtual, self-consistent cryptographic keys.
> **This is a personal fork of [JingMatrix/TEESimulator](https://github.com/JingMatrix/TEESimulator)** with additional hardening, native Rust certificate generation, key persistence, and anti-detection features. For the upstream project, see the original repo.
--- ## ✨ Core Principles
## 🧬 What is TEESimulator? * **Bypass Hardware-Backed Attestation:** The primary goal of this project is to defeat Key Attestation, a security mechanism that allows apps to verify that they are running on a secure, unmodified device. This module provides the tools to bypass these checks on rooted or modified devices.
* **Stateful Emulation:** Instead of patching responses from the real TEE, the ultimate goal is to create and manage virtual keys entirely in a simulated software environment. Any request concerning a virtual key will be handled by the simulator, ensuring perfect consistency without ever touching the real hardware.
* **Architectural Interception:** By hooking low-level Binder IPC calls to the Keystore, the framework can transparently redirect requests for virtual keys to the software-based simulator, while allowing requests for real keys to pass through to the hardware TEE.
* **100% FOSS:** Licensed under GPLv3, ensuring it stays free, auditable, and compliant with open-source laws.
TEESimulator is a **complete software simulation** of Android's hardware-backed [Trusted Execution Environment](https://source.android.com/docs/security/features/trusty) for [Key Attestation](https://developer.android.com/privacy-and-security/security-key-attestation). Instead of patching certificates from the real TEE after the fact, TEESimulator intercepts Binder IPC at the `ioctl` level and generates entire certificate chains from scratch — signed by your keybox, with correct attestation extensions, indistinguishable from hardware-generated keys. ## 📱 Requirements
- Android 10 or above
The result: **apps that verify hardware attestation see a legitimate, unmodified device** — even on rooted hardware with an unlocked bootloader. ## 📦 Installation & Configuration
> **This is not TrickyStore.** TEESimulator replaces TrickyStore and its forks entirely. It shares the same config paths for drop-in compatibility, but the architecture is fundamentally different: native Rust certificate generation, binder-level interception via `lsplt`, per-UID rate limiting, key persistence, and a multi-layer defense against detector apps. 1. Flash this module via (Magisk / KernelSU / APatch) and reboot.
2. (Optional) Place a hardware-backed `keybox.xml` at `/data/adb/tricky_store/keybox.xml`. This provides the cryptographic "root of trust" for the simulator.
3. (Optional) Customize target packages in `/data/adb/tricky_store/target.txt`.
4. (Optional) Customize the simulated security patch level in `/data/adb/tricky_store/security_patch.txt`.
5. Enjoy!
--- **All configuration files are monitored and will take effect immediately upon saving.**
## 🔥 Why TEESimulator?
🔐 **Native Cert Generation** — v4.0 generates X.509 certificate chains in Rust with `ring` and manual DER encoding. No BouncyCastle overhead, no Java crypto quirks, byte-perfect issuer chain linkage.
🎯 **Binder-Level Interception** — Hooks `ioctl()` on `libc.so` via `lsplt` inside the `keystore2` process. Intercepts `generateKey`, `importKey`, and `getKeyEntry` transactions before the HAL ever sees them.
🛡️ **Detector Resistant** — Per-UID rate limiting blocks DuckDetector-style keygen flooding. Oversized challenges rejected with real KeyMint error codes. Chain consistency verified byte-for-byte.
💾 **Key Persistence** — Generated keys survive reboots. Apps that store attestation keys (banking, biometrics) don't break after a restart.
🔧 **Drop-In Replacement** — Same config paths as TrickyStore (`/data/adb/tricky_store/`). Swap the module ZIP, keep your keybox and target list.
---
## ✨ Features
**Core Attestation Engine**
- [x] **Full certificate chain generation** — leaf + intermediates + root, signed by your keybox
- [x] **Native Rust certgen**`libcertgen.so` built with `ring`, `rsa`, and manual DER assembly
- [x] **BouncyCastle fallback** — unsupported curves (P-224, P-521, Curve25519) fall back to Java
- [x] **ASN.1 attestation extensions** — OID 1.3.6.1.4.1.11129.2.1.17 with all AOSP-specified tags
- [x] **Multi-keybox support** — different keybox files per app group via `target.txt`
**Interception Layer**
- [x] **Binder ioctl hook**`lsplt` PLT hook on `libc.so` inside `keystore2` process
- [x] **generateKey / importKey / getKeyEntry** — all three transaction types intercepted
- [x] **256KB native payload cap** — oversized binder payloads bypass interception cleanly
- [x] **Challenge validation** — rejects >128-byte attestation challenges with `INVALID_INPUT_LENGTH`
**Hardening**
- [x] **Per-UID rate limiter** — 2 hardware keygens per 30s burst window, software fallback on overflow
- [x] **importKey eviction guard** — retained patch chains prevent generate-then-import cache attacks
- [x] **Key persistence** — file-backed storage with file-level locking, survives reboots and keybox rotations
- [x] **Global exception handler** — uncaught exceptions logged, daemon stays alive
**Configuration**
- [x] **Live config reload**`FileObserver` watches all config files, changes apply immediately
- [x] **Security patch spoofing** — per-package `system`, `vendor`, `boot` patch levels with dynamic templates
- [x] **Lifecycle scripts** — KSU Action button clears key cache, uninstall removes all traces
---
## 📋 Requirements
> [!IMPORTANT]
> TEESimulator requires root access and a valid `keybox.xml` for hardware-level attestation results. Without a keybox, the module generates software-level certificates that won't pass strict hardware attestation checks.
**You need:**
1. Android 10 or above
2. A supported root manager (KernelSU, Magisk, or APatch)
3. A hardware-backed `keybox.xml` placed at `/data/adb/tricky_store/keybox.xml`
---
## 📱 Compatibility
### Root Managers
| Manager | Status | Notes |
|---|---|---|
| KernelSU | ✅ Tested | Full support including Action button and lifecycle scripts |
| Magisk | ✅ Supported | Standard module install |
| APatch | ✅ Supported | Standard module install |
### Tested Devices
| Device | Android | TEE | Status |
|---|---|---|---|
| Redmi 14C (2409BRN2CA) | 14 (SDK 34) | Beanpod KeyMaster | ✅ Daily driver |
> Tested against DuckDetector, Luna, Play Integrity, and Key Attestation Demo. If you test on a different device, [open an issue](https://github.com/Enginex0/TEESimulator/issues) with your results.
---
## 🚀 Quick Start
1. **Download** the latest release ZIP from [Releases](https://github.com/Enginex0/TEESimulator/releases)
2. **Install** via your root manager (KSU / Magisk / APatch) and reboot
3. **Place your keybox** at `/data/adb/tricky_store/keybox.xml`
4. **Configure targets** in `/data/adb/tricky_store/target.txt`
5. **Verify** — check Play Integrity or run Key Attestation Demo
TEESimulator replaces TrickyStore, TrickyStoreOSS, and their forks. Existing config files are compatible.
---
## 🔨 Building from Source
The CI workflow builds on every push to `main`. You can also build locally or trigger a build from your own fork.
**Prerequisites:** JDK 21, Android SDK/NDK 27, Rust stable with `aarch64-linux-android` target, `cargo-ndk`.
```bash
git clone https://github.com/Enginex0/TEESimulator.git
cd TEESimulator
./gradlew zipRelease zipDebug
```
Output ZIPs land in `out/`. The Gradle build automatically invokes `cargo ndk` to cross-compile `libcertgen.so` before packaging.
To rebuild from a fork, push to `main` or use **Actions → Build → Run workflow**. The workflow installs all toolchains (Java, Rust, cargo-ndk, ccache) and uploads Release + Debug ZIPs as artifacts.
---
## ⚙️ Configuration
All configuration files live at `/data/adb/tricky_store/` and are monitored by `FileObserver` — changes take effect immediately without rebooting.
### The `keybox.xml` Root of Trust ### The `keybox.xml` Root of Trust
This file provides the master cryptographic identity. It contains a private key and a hardware-backed certificate chain from a real device. TEESimulator signs all generated certificates with this key, making them appear legitimate to verifiers. This file provides the master cryptographic identity for the simulator. It contains a private key and a valid, hardware-backed certificate chain from a real device. The simulator uses this to sign the virtual certificates it generates, making them appear legitimate to verifiers.
```xml ```xml
<?xml version="1.0"?> <?xml version="1.0"?>
@@ -151,106 +40,51 @@ This file provides the master cryptographic identity. It contains a private key
</AndroidAttestation> </AndroidAttestation>
``` ```
### Target Packages (`target.txt`) ### Mode and Keybox Configuration (`target.txt`)
Controls which apps get intercepted and what simulation mode to use. TEESimulator currently operates in two primary modes as it transitions towards full emulation.
You can control the simulation mode and the specific keybox.xml file used on a per-package basis.
#### Mode Suffixes #### Mode Suffixes
* **`!` → Force Generation** Creates a complete software-based virtual key. Full TEE simulation. * **`!` → Force Generation Mode:** Creates a complete, software-based virtual key. This is the foundation of the full TEE simulation.
* **`?` → Force Leaf Hacking** — Real TEE key generated, but its attestation certificate is intercepted and patched. * **`?` → Force Leaf Hacking Mode:** A legacy mode where a real TEE key is generated, but its attestation certificate is intercepted and modified.
* **No symbol → Automatic** — Module selects the best mode for your device. * **No symbol → Automatic Mode:** The module selects the most appropriate mode for the device.
#### Multi-Keybox #### Multi-Keybox Configuration
Specify different keybox files for different app groups. Apps listed after a `[filename.xml]` line use that keybox. Apps before any declaration use the default `keybox.xml`. You can specify different keybox files for different groups of applications. This is done by adding a line with the filename in square brackets (e.g., [demo_keybox.xml]).
All applications listed after this line will use the specified keybox file, until a new keybox is declared. Applications listed before any custom keybox declaration will use the default `keybox.xml`.
For example:
``` ```
# Default keybox # These two apps will use the default /data/adb/tricky_store/keybox.xml
com.google.android.gms! com.google.android.gms!
io.github.vvb2060.keyattestation? io.github.vvb2060.keyattestation?
# Switch to a different keybox for the following apps # Switch to a different keybox for the following apps.
# The file must be located at /data/adb/tricky_store/aosp_keybox.xml
[aosp_keybox.xml] [aosp_keybox.xml]
com.google.android.gsf com.google.android.gsf
# Another keybox # Switch again to another keybox.
# The file must be located at /data/adb/tricky_store/demo_keybox.xml
[demo_keybox.xml] [demo_keybox.xml]
org.matrix.demo org.matrix.demo
``` ```
### Security Patch Level (`security_patch.txt`) ### Security Patch Level (`security_patch.txt`)
Configure the `osPatchLevel`, `vendorPatchLevel`, and `bootPatchLevel` reported in attestation certificates. This only affects attestation data — it does not change actual system properties. This allows you to configure the security patch level that the simulator will report in its forged attestation certificates.
#### Global and Per-Package
Settings at the top of the file are global defaults. Add `[package.name]` to override for specific apps.
#### Keys
| Key | Scope |
|---|---|
| `system` | OS patch level |
| `vendor` | Vendor patch level |
| `boot` | Boot/kernel patch level |
| `all` | Shorthand — sets all three at once |
#### Special Keywords
| Keyword | Effect |
|---|---|
| `today` | Current date, dynamically resolved on each attestation |
| `YYYY-MM-DD` templates | Semi-dynamic — `YYYY-MM-05` resolves to the 5th of the current month |
| `no` | Omit this patch level tag entirely from the attestation |
| `device_default` | Use the device's real hardware value |
| `prop` | Read from `ro.build.version.security_patch` (matches what detectors see via getprop) |
#### Example
``` ```
# Global — default for all apps # Advanced Configuration
system=YYYY-MM-05 system=2025-11
vendor=device_default boot=no # Do not report a boot patch level
boot=no vendor=20251101 # Report a specific vendor patch level
# Override for GMS
[com.google.android.gms]
system=2024-10-01
# Custom config for a demo app
[org.matrix.demo]
all=2025-09-15
boot=device_default
``` ```
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change system properties.
--- ## 🤝 Contributions
PRs are welcome as we work towards the goal of a complete TEE simulation. Thank you for supporting true open-source development.
## 💬 Community
<p align="center">
<a href="https://t.me/superpowers9">
<img src="https://img.shields.io/badge/⚡_JOIN_THE_GRID-SuperPowers_Telegram-black?style=for-the-badge&logo=telegram&logoColor=cyan&labelColor=0d1117&color=00d4ff" alt="Telegram">
</a>
</p>
---
## 🙏 Credits
- **[JingMatrix](https://github.com/JingMatrix/TEESimulator)** — original author of TEESimulator and the interception architecture
- **[5ec1cff](https://github.com/5ec1cff/TrickyStore)** — TrickyStore, the project that pioneered keystore interception on Android
- **[LSPlt](https://github.com/LSPosed/LSPlt)** — PLT hook library used for binder interception
- **[ring](https://github.com/briansmith/ring)** — Rust cryptography library powering native cert generation
---
## 📄 License
This project is licensed under the [GNU General Public License v3.0](LICENSE).
---
<p align="center">
<b>🔐 Because the best attestation is the one the TEE never generated.</b>
</p>
+1
View File
@@ -0,0 +1 @@
/build
+62 -67
View File
@@ -1,7 +1,9 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import com.android.build.api.artifact.SingleArtifact import com.android.build.api.artifact.SingleArtifact
import java.io.ByteArrayOutputStream
import javax.inject.Inject
import org.gradle.process.ExecOperations
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
@@ -11,30 +13,24 @@ plugins {
ktfmt { kotlinLangStyle() } ktfmt { kotlinLangStyle() }
// Helper class to get access to the ExecOperations service fun String.execute(currentWorkingDir: File = File("./")): String {
abstract class GitExecutor @Inject constructor(private val execOperations: ExecOperations) { val parts = this.split("\\s+".toRegex())
fun execute(command: String, currentWorkingDir: File): String { val process =
val byteOut = ByteArrayOutputStream() ProcessBuilder(parts).directory(currentWorkingDir).redirectErrorStream(true).start()
execOperations.exec {
workingDir = currentWorkingDir val output = process.inputStream.bufferedReader().readText()
commandLine = command.split("\\s".toRegex()) process.waitFor()
standardOutput = byteOut return output.trim()
}
return String(byteOut.toByteArray()).trim()
}
} }
// Instantiate the helper class using Gradle's object factory val gitCommitCount = "git rev-list HEAD --count".execute().toInt()
val gitExecutor = objects.newInstance(GitExecutor::class.java) val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
val verName = "v1.0"
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
val verName = "v4.4"
android { android {
namespace = "org.matrix.TEESimulator" namespace = "org.matrix.TEESimulator"
compileSdk = 36 compileSdk = 36
ndkVersion = "27.3.13750724" ndkVersion = "28.2.13676358"
buildToolsVersion = "36.0.0" buildToolsVersion = "36.0.0"
defaultConfig { defaultConfig {
@@ -43,61 +39,59 @@ android {
targetSdk = 36 targetSdk = 36
versionCode = gitCommitCount versionCode = gitCommitCount
versionName = verName versionName = verName
}
buildTypes { externalNativeBuild {
release { cmake {
isMinifyEnabled = true arguments += "-DANDROID_STL=none"
proguardFiles("proguard-rules.pro") arguments += "-DCMAKE_BUILD_TYPE=Release"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
arguments += "-DANDROID_ALLOW_UNDEFINED_SYMBOLS=ON"
arguments += "-DCMAKE_CXX_STANDARD=23"
arguments += "-DCMAKE_C_STANDARD=23"
arguments += "-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON"
arguments += "-DLSPLT_BUILD_SHARED=OFF"
arguments += "-DLSPLT_STANDALONE=ON"
cppFlags += "-std=c++23"
cppFlags += "-fno-exceptions"
cppFlags += "-fno-rtti"
cppFlags += "-fvisibility=hidden"
cppFlags += "-fvisibility-inlines-hidden"
}
} }
} }
buildFeatures { prefab = true }
buildTypes {
debug { isMinifyEnabled = false }
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_21 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21
} }
buildFeatures { buildConfig = true }
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path = file("src/main/cpp/CMakeLists.txt") path = file("src/main/cpp/CMakeLists.txt")
buildStagingDirectory = layout.buildDirectory.get().asFile buildStagingDirectory = layout.buildDirectory.get().asFile
version = "3.28.0+"
} }
} }
buildFeatures { viewBinding = false }
} }
dependencies { dependencies {
compileOnly(project(":stub")) compileOnly(project(":stub"))
compileOnly(libs.annotation) compileOnly(libs.annotation)
implementation(libs.bcpkix) implementation(libs.org.bouncycastle.bcpkix.jdk18on)
} implementation(libs.org.lsposed.libcxx.libcxx)
// --- Rust native cert gen build task ---
val buildRustCertgen by tasks.registering(Exec::class) {
group = "TEESimulator 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 {
@@ -121,10 +115,12 @@ androidComponents {
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")
// The Sync task will automatically depend on the tasks that produce these
// artifacts.
// This is the correct way to establish the dependency chain.
if (isDebug) { if (isDebug) {
from(variant.artifacts.get(SingleArtifact.APK)) { from(variant.artifacts.get(SingleArtifact.APK)) {
include("*.apk") include("*.apk")
@@ -140,14 +136,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") // Place them in the 'lib' subfolder of the staging directory.
into("lib") include("**/libinject.so", "**/libTEESimulator.so")
include("**/libinject.so", "**/libTEESimulator.so", "**/libsupervisor.so", "**/libcertgen.so")
} }
// Now, copy and process the files from 'module' directory. // Now, copy and process the files from 'module' directory.
+85 -6
View File
@@ -1,15 +1,94 @@
-keep class org.matrix.TEESimulator.interception.keystore.** { *; } # Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keepclasseswithmembers class io.github.beakthoven.TrickyStoreOSS.MainKt {
public static void main(java.lang.String[]);
}
-assumenosideeffects class io.github.beakthoven.TrickyStoreOSS.logging.Logger {
public static void d(java.lang.String);
public static void dd(java.lang.String);
public static void v(java.lang.String);
}
-assumenosideeffects class android.util.Log {
public static int v(...);
public static int d(...);
}
# keep these or bouncycastle will not work
-keep class org.bouncycastle.jcajce.provider.** { *; } -keep class org.bouncycastle.jcajce.provider.** { *; }
-keep class org.bouncycastle.jce.provider.** { *; } -keep class org.bouncycastle.jce.provider.** { *; }
-dontwarn javax.naming.** -dontwarn javax.naming.**
-keepclasseswithmembers class org.matrix.TEESimulator.App { # Keep `Companion` object fields of serializable classes.
public static void main(java.lang.String[]); # This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
} }
-keepclasseswithmembers class org.matrix.TEESimulator.pki.NativeCertGen { # Keep `serializer()` on companion objects (both default and named) of serializable classes.
native <methods>; -if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
# Keep all interceptor classes and their methods - used via reflection and JNI
-keep class io.github.beakthoven.TrickyStoreOSS.interceptors.** {
*; *;
} }
-keep class org.matrix.TEESimulator.pki.CertGenConfig { *; }
# Keep SecurityLevelInterceptor and its inner classes
-keep class io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor {
*;
}
# Keep Key and Info inner classes used in maps - critical for runtime
-keepclassmembers class io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor$Key {
*;
}
-keepclassmembers class io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor$Info {
*;
}
# Keep Parcelable CREATOR fields
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}
-repackageclasses
-allowaccessmodification
-overloadaggressively
-keepattributes SourceFile,LineNumberTable,LocalVariableTable
-renamesourcefileattribute
+6 -1
View File
@@ -1,2 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest /> <!--
Copyright 2025 Dakkshesh <beakthoven@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
-->
<manifest/>
+15 -14
View File
@@ -1,31 +1,32 @@
cmake_minimum_required(VERSION 3.10) # Copyright 2025 Dakkshesh <beakthoven@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
cmake_minimum_required(VERSION 3.28)
project(TEESimulator) project(TEESimulator)
set(CMAKE_CXX_STANDARD 23) find_package(cxx REQUIRED CONFIG)
link_libraries(cxx::cxx)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") add_library(my_logging STATIC logging/logging.cpp)
target_include_directories(my_logging PUBLIC logging/include)
target_link_libraries(my_logging log)
# LSPlt configuration # LSPlt configuration
OPTION(LSPLT_BUILD_SHARED OFF)
add_subdirectory(external/LSPlt/lsplt/src/main/jni) add_subdirectory(external/LSPlt/lsplt/src/main/jni)
add_compile_definitions(BINDER_DISABLE_NATIVE_HANDLE) # libutils stub
add_library(utils SHARED stub/stub_utils.cpp) add_library(utils SHARED stub/stub_utils.cpp)
target_include_directories(utils PUBLIC external/AOSP/include) target_include_directories(utils PUBLIC external/AOSP/include)
# libbinder stub
add_library(binder SHARED stub/stub_binder.cpp) add_library(binder SHARED stub/stub_binder.cpp)
target_include_directories(binder PUBLIC external/AOSP/include) target_include_directories(binder PUBLIC external/AOSP/include)
target_link_libraries(binder PRIVATE utils) target_link_libraries(binder PRIVATE utils)
add_executable(libinject.so inject/main.cpp inject/utils.cpp) add_executable(libinject.so inject/main.cpp inject/utils.cpp)
target_include_directories(libinject.so PUBLIC include) target_link_libraries(libinject.so PRIVATE lsplt_static my_logging)
target_link_libraries(libinject.so PRIVATE lsplt_static)
add_executable(libsupervisor.so supervisor.cpp)
target_link_libraries(libsupervisor.so PRIVATE log)
add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp) add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp)
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include include) target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE binder lsplt_static utils) target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE log binder utils lsplt_static my_logging)
File diff suppressed because it is too large Load Diff
@@ -1,83 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
// WARNING: deprecated - DO NOT USE - prefer to setup service directly.
//
// This class embellishes a class with a few static methods which can be used in
// limited circumstances (when one service needs to be registered and
// published). However, this is an anti-pattern:
// - these methods are aliases of existing methods, and as such, represent an
// incremental amount of information required to understand the system but
// which does not actually save in terms of lines of code. For instance, users
// of this class should be surprised to know that this will start up to 16
// threads in the binder threadpool.
// - the template instantiation costs need to be paid, even though everything
// done here is generic.
// - the getServiceName API here is undocumented and non-local (for instance,
// this unnecessarily assumes a single service type will only be instantiated
// once with no arguments).
//
// So, DO NOT USE.
// ---------------------------------------------------------------------------
namespace android {
template<typename SERVICE>
class BinderService
{
public:
static status_t publish(bool allowIsolated = false,
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
sp<IServiceManager> sm(defaultServiceManager());
return sm->addService(String16(SERVICE::getServiceName()), new SERVICE(), allowIsolated,
dumpFlags);
}
static void publishAndJoinThreadPool(
bool allowIsolated = false,
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
publish(allowIsolated, dumpFlags);
joinThreadPool();
}
static void instantiate() { publish(); }
static status_t shutdown() { return NO_ERROR; }
private:
static void joinThreadPool() {
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
ps->giveThreadPoolName();
IPCThreadState::self()->joinThreadPool();
}
};
} // namespace android
// ---------------------------------------------------------------------------
+1 -4
View File
@@ -39,11 +39,8 @@
// //
// For a more detailed explanation of this strategy, see // For a more detailed explanation of this strategy, see
// https://www.gnu.org/software/gnulib/manual/html_node/Exported-Symbols-of-Shared-Libraries.html // https://www.gnu.org/software/gnulib/manual/html_node/Exported-Symbols-of-Shared-Libraries.html
#if BUILDING_LIBBINDER
#define LIBBINDER_EXPORTED __attribute__((__visibility__("default"))) #define LIBBINDER_EXPORTED __attribute__((__visibility__("default")))
#else
#define LIBBINDER_EXPORTED
#endif
// For stuff that is exported but probably shouldn't be. It behaves the exact // For stuff that is exported but probably shouldn't be. It behaves the exact
// same way as LIBBINDER_EXPORTED, only exists to help track what we want // same way as LIBBINDER_EXPORTED, only exists to help track what we want
@@ -1,99 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#if !defined(__BIONIC__) && defined(BINDER_ENABLE_LIBLOG_ASSERT)
#include <log/log.h>
#define __assert(file, line, message) LOG_ALWAYS_FATAL(file ":" #line ": " message)
#endif
#ifndef __BIONIC__
#ifndef __assert
// defined differently by liblog
#pragma push_macro("LOG_PRI")
#ifdef LOG_PRI
#undef LOG_PRI
#endif
#include <syslog.h>
#pragma pop_macro("LOG_PRI")
#define __assert(a, b, c) \
do { \
syslog(LOG_ERR, a ": " c); \
abort(); \
} while (false)
#endif // __assert
#endif // __BIONIC__
namespace android {
/*
* Used to manage AIDL's *Delegator types.
* This is used to:
* - create a new *Delegator object that delegates to the binder argument.
* - or return an existing *Delegator object that already delegates to the
* binder argument.
* - or return the underlying delegate binder if the binder argument is a
* *Delegator itself.
*
* @param binder - the binder to delegate to or unwrap
*
* @return pointer to the *Delegator object or the unwrapped binder object
*/
template <typename T>
sp<T> delegate(const sp<T>& binder) {
const void* isDelegatorId = &T::descriptor;
const void* hasDelegatorId = &T::descriptor + 1;
// is binder itself a delegator?
if (T::asBinder(binder)->findObject(isDelegatorId)) {
if (T::asBinder(binder)->findObject(hasDelegatorId)) {
__assert(__FILE__, __LINE__,
"This binder has a delegator and is also delegator itself! This is "
"likely an unintended mixing of binders.");
return nullptr;
}
// unwrap the delegator
return static_cast<typename T::DefaultDelegator*>(binder.get())->getImpl();
}
struct MakeArgs {
const sp<T>* binder;
const void* id;
} makeArgs;
makeArgs.binder = &binder;
makeArgs.id = isDelegatorId;
// the binder is not a delegator, so construct one
sp<IBinder> newDelegator = T::asBinder(binder)->lookupOrCreateWeak(
hasDelegatorId,
[](const void* args) -> sp<IBinder> {
auto delegator = sp<typename T::DefaultDelegator>::make(
*static_cast<const MakeArgs*>(args)->binder);
// make sure we know this binder is a delegator by attaching a unique ID
(void)delegator->attachObject(static_cast<const MakeArgs*>(args)->id,
reinterpret_cast<void*>(0x1), nullptr, nullptr);
return delegator;
},
static_cast<const void*>(&makeArgs));
return sp<typename T::DefaultDelegator>::cast(newDelegator);
}
} // namespace android
-42
View File
@@ -1,42 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <iterator>
#include <type_traits>
namespace android {
namespace internal {
// Never instantiated. Used as a placeholder for template variables.
template <typename T>
struct invalid_type;
// AIDL generates specializations of this for enums.
template <typename EnumType, typename = std::enable_if_t<std::is_enum<EnumType>::value>>
constexpr invalid_type<EnumType> enum_values;
} // namespace internal
// Usage: for (const auto v : enum_range<EnumType>() ) { ... }
template <typename EnumType, typename = std::enable_if_t<std::is_enum<EnumType>::value>>
struct enum_range {
constexpr auto begin() const { return std::begin(internal::enum_values<EnumType>); }
constexpr auto end() const { return std::end(internal::enum_values<EnumType>); }
};
} // namespace android
@@ -1,71 +0,0 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <functional>
#include <optional>
namespace android::binder::impl {
template <typename F>
class scope_guard;
template <typename F>
scope_guard<F> make_scope_guard(F f);
template <typename F>
class scope_guard {
public:
inline ~scope_guard() {
if (f_.has_value()) std::move(f_.value())();
}
inline void release() { f_.reset(); }
private:
friend scope_guard<F> android::binder::impl::make_scope_guard<>(F);
inline scope_guard(F&& f) : f_(std::move(f)) {}
std::optional<F> f_;
};
template <typename F>
inline scope_guard<F> make_scope_guard(F f) {
return scope_guard<F>(std::move(f));
}
template <typename F>
constexpr void assert_small_callable() {
// While this buffer (std::function::__func::__buf_) is an implementation detail generally not
// accessible to users, it's a good bet to assume its size to be around 3 pointers.
constexpr size_t kFunctionBufferSize = 3 * sizeof(void*);
static_assert(sizeof(F) <= kFunctionBufferSize,
"Supplied callable is larger than std::function optimization buffer. "
"Try using std::ref, but make sure lambda lives long enough to be called.");
}
template <typename T>
class SmallFunction : public std::function<T> {
public:
template <typename F>
SmallFunction(F&& f) : std::function<T>(f) {
assert_small_callable<F>();
}
};
} // namespace android::binder::impl
@@ -263,7 +263,6 @@ constexpr const char* const kManualInterfaces[] = {
"android.utils.IMemory", "android.utils.IMemory",
"android.utils.IMemoryHeap", "android.utils.IMemoryHeap",
"com.android.car.procfsinspector.IProcfsInspector", "com.android.car.procfsinspector.IProcfsInspector",
"com.android.internal.app.IAppOpsCallback",
"com.android.internal.app.IAppOpsService", "com.android.internal.app.IAppOpsService",
"com.android.internal.app.IBatteryStats", "com.android.internal.app.IBatteryStats",
"com.android.internal.os.IResultReceiver", "com.android.internal.os.IResultReceiver",
-122
View File
@@ -1,122 +0,0 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <utils/RefBase.h>
#include <utils/Errors.h>
#include <binder/Common.h>
#include <binder/IInterface.h>
namespace android {
// ----------------------------------------------------------------------------
class LIBBINDER_EXPORTED IMemoryHeap : public IInterface {
public:
DECLARE_META_INTERFACE(MemoryHeap)
// flags returned by getFlags()
enum {
READ_ONLY = 0x00000001
};
virtual int getHeapID() const = 0;
virtual void* getBase() const = 0;
virtual size_t getSize() const = 0;
virtual uint32_t getFlags() const = 0;
virtual off_t getOffset() const = 0;
// these are there just for backward source compatibility
int32_t heapID() const { return getHeapID(); }
void* base() const { return getBase(); }
size_t virtualSize() const { return getSize(); }
};
class LIBBINDER_EXPORTED BnMemoryHeap : public BnInterface<IMemoryHeap> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact(
uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
BnMemoryHeap();
protected:
virtual ~BnMemoryHeap();
};
// ----------------------------------------------------------------------------
class LIBBINDER_EXPORTED IMemory : public IInterface {
public:
DECLARE_META_INTERFACE(Memory)
// NOLINTNEXTLINE(google-default-arguments)
virtual sp<IMemoryHeap> getMemory(ssize_t* offset=nullptr, size_t* size=nullptr) const = 0;
// helpers
// Accessing the underlying pointer must be done with caution, as there are
// some inherent security risks associated with it. When receiving an
// IMemory from an untrusted process, there is currently no way to guarantee
// that this process would't change the content after the fact. This may
// lead to TOC/TOU class of security bugs. In most cases, when performance
// is not an issue, the recommended practice is to immediately copy the
// buffer upon reception, then work with the copy, e.g.:
//
// std::string private_copy(mem.size(), '\0');
// memcpy(private_copy.data(), mem.unsecurePointer(), mem.size());
//
// In cases where performance is an issue, this matter must be addressed on
// an ad-hoc basis.
void* unsecurePointer() const;
size_t size() const;
ssize_t offset() const;
private:
// These are now deprecated and are left here for backward-compatibility
// with prebuilts that may reference these symbol at runtime.
// Instead, new code should use unsecurePointer()/unsecureFastPointer(),
// which do the same thing, but make it more obvious that there are some
// security-related pitfalls associated with them.
void* pointer() const;
void* fastPointer(const sp<IBinder>& heap, ssize_t offset) const;
};
class LIBBINDER_EXPORTED BnMemory : public BnInterface<IMemory> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact(
uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
BnMemory();
protected:
virtual ~BnMemory();
};
// ----------------------------------------------------------------------------
} // namespace android
@@ -64,7 +64,10 @@ public:
* Returns the PID of the process which has made the current binder * Returns the PID of the process which has made the current binder
* call. If not in a binder call, this will return getpid. * call. If not in a binder call, this will return getpid.
* *
* Warning: oneway transactions do not receive PID. Even if you expect * Warning do not use this as a security identifier! PID is unreliable
* as it may be re-used. This should mostly be used for debugging.
*
* oneway transactions do not receive PID. Even if you expect
* a transaction to be synchronous, a misbehaving client could send it * a transaction to be synchronous, a misbehaving client could send it
* as an asynchronous call and result in a 0 PID here. Additionally, if * as an asynchronous call and result in a 0 PID here. Additionally, if
* there is a race and the calling process dies, the PID may still be * there is a race and the calling process dies, the PID may still be
@@ -1,69 +0,0 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifndef __ANDROID_VNDK__
#include <binder/Common.h>
#include <binder/IInterface.h>
#include <stdlib.h>
namespace android {
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED IPermissionController : public IInterface {
public:
DECLARE_META_INTERFACE(PermissionController)
virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid) = 0;
virtual int32_t noteOp(const String16& op, int32_t uid, const String16& packageName) = 0;
virtual void getPackagesForUid(const uid_t uid, Vector<String16> &packages) = 0;
virtual bool isRuntimePermission(const String16& permission) = 0;
virtual int getPackageUid(const String16& package, int flags) = 0;
enum {
CHECK_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
NOTE_OP_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 1,
GET_PACKAGES_FOR_UID_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 2,
IS_RUNTIME_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 3,
GET_PACKAGE_UID_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 4
};
};
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED BnPermissionController : public BnInterface<IPermissionController> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
// ----------------------------------------------------------------------
} // namespace android
#else // __ANDROID_VNDK__
#error "This header is not visible to vendors"
#endif // __ANDROID_VNDK__
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IInterface.h>
namespace android {
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED IResultReceiver : public IInterface {
public:
DECLARE_META_INTERFACE(ResultReceiver)
virtual void send(int32_t resultCode) = 0;
enum {
OP_SEND = IBinder::FIRST_CALL_TRANSACTION
};
};
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED BnResultReceiver : public BnInterface<IResultReceiver> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
// ----------------------------------------------------------------------
} // namespace android
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <android/os/IServiceManager.h>
namespace android::impl {
LIBBINDER_EXPORTED sp<android::os::IServiceManager>
getJavaServicemanagerImplPrivateDoNotUseExceptInTheOnePlaceItIsUsed();
} // namespace android::impl
@@ -1,29 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <android/os/IServiceManager.h>
#include "IServiceManager.h"
namespace android {
/**
* Encapsulate an AidlServiceManager in a CppBackendShim. Only used for testing.
*/
LIBBINDER_EXPORTED sp<IServiceManager> getServiceManagerShimFromAidlServiceManagerForTests(
const sp<os::IServiceManager>& sm);
} // namespace android
@@ -1,51 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IInterface.h>
namespace android {
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED IShellCallback : public IInterface {
public:
DECLARE_META_INTERFACE(ShellCallback)
virtual int openFile(const String16& path, const String16& seLinuxContext,
const String16& mode) = 0;
enum {
OP_OPEN_OUTPUT_FILE = IBinder::FIRST_CALL_TRANSACTION
};
};
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED BnShellCallback : public BnInterface<IShellCallback> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
// ----------------------------------------------------------------------
} // namespace android
@@ -1,114 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <functional>
#include <binder/Common.h>
#include <binder/IServiceManager.h>
#include <binder/Status.h>
#include <utils/StrongPointer.h>
namespace android {
namespace binder {
namespace internal {
class ClientCounterCallback;
} // namespace internal
/**
* Exits when all services registered through this object have 0 clients
*
* In order to use this class, it's expected that your service:
* - registers all services in the process with this API
* - configures services as oneshot in init .rc files
* - configures services as disabled in init.rc files, unless a client is
* guaranteed early in boot, in which case, forcePersist should also be used
* to avoid races.
* - uses 'interface' declarations in init .rc files
*
* For more information on init .rc configuration, see system/core/init/README.md
**/
class LazyServiceRegistrar {
public:
LIBBINDER_EXPORTED static LazyServiceRegistrar& getInstance();
LIBBINDER_EXPORTED status_t
registerService(const sp<IBinder>& service, const std::string& name = "default",
bool allowIsolated = false,
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
/**
* Force the service to persist, even when it has 0 clients.
* If setting this flag from the server side, make sure to do so before calling
* registerService, or there may be a race with the default dynamic shutdown.
*
* This should only be used if it is every eventually set to false. If a
* service needs to persist but doesn't need to dynamically shut down,
* prefer to control it with another mechanism such as ctl.start.
*/
LIBBINDER_EXPORTED void forcePersist(bool persist);
/**
* Set a callback that is invoked when the active service count (i.e. services with clients)
* registered with this process drops to zero (or becomes nonzero).
* The callback takes a boolean argument, which is 'true' if there is
* at least one service with clients.
*
* Callback return value:
* - false: Default behavior for lazy services (shut down the process if there
* are no clients).
* - true: Don't shut down the process even if there are no clients.
*
* This callback gives a chance to:
* 1 - Perform some additional operations before exiting;
* 2 - Prevent the process from exiting by returning "true" from the
* callback.
*
* This method should be called before 'registerService' to avoid races.
*/
LIBBINDER_EXPORTED void setActiveServicesCallback(
const std::function<bool(bool)>& activeServicesCallback);
/**
* Try to unregister all services previously registered with 'registerService'.
* Returns 'true' if successful. This should only be called within the callback registered by
* setActiveServicesCallback.
*/
LIBBINDER_EXPORTED bool tryUnregister();
/**
* Re-register services that were unregistered by 'tryUnregister'.
* This method should be called in the case 'tryUnregister' fails
* (and should be called on the same thread).
*/
LIBBINDER_EXPORTED void reRegister();
/**
* Create a second instance of lazy service registrar.
*
* WARNING: dangerous! DO NOT USE THIS - LazyServiceRegistrar
* should be single-instanced, so that the service will only
* shut down when all services are unused. A separate instance
* is only used to test race conditions.
*/
LIBBINDER_EXPORTED static LazyServiceRegistrar createExtraTestInstance();
private:
std::shared_ptr<internal::ClientCounterCallback> mClientCC;
LazyServiceRegistrar();
};
} // namespace binder
} // namespace android
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <binder/Common.h>
#include <binder/IMemory.h>
namespace android {
// ---------------------------------------------------------------------------
class LIBBINDER_EXPORTED MemoryBase : public BnMemory {
public:
MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size);
virtual ~MemoryBase();
virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const;
protected:
size_t getSize() const { return mSize; }
ssize_t getOffset() const { return mOffset; }
const sp<IMemoryHeap>& getHeap() const { return mHeap; }
private:
size_t mSize;
ssize_t mOffset;
sp<IMemoryHeap> mHeap;
};
// ---------------------------------------------------------------------------
} // namespace android
@@ -1,61 +0,0 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include <binder/Common.h>
#include <binder/IMemory.h>
#include <binder/MemoryHeapBase.h>
namespace android {
// ----------------------------------------------------------------------------
class SimpleBestFitAllocator;
// ----------------------------------------------------------------------------
class MemoryDealer : public RefBase {
public:
LIBBINDER_EXPORTED explicit MemoryDealer(
size_t size, const char* name = nullptr,
uint32_t flags = 0 /* or bits such as MemoryHeapBase::READ_ONLY */);
LIBBINDER_EXPORTED virtual sp<IMemory> allocate(size_t size);
LIBBINDER_EXPORTED virtual void dump(const char* what) const;
// allocations are aligned to some value. return that value so clients can account for it.
LIBBINDER_EXPORTED static size_t getAllocationAlignment();
sp<IMemoryHeap> getMemoryHeap() const { return heap(); }
protected:
LIBBINDER_EXPORTED virtual ~MemoryDealer();
private:
friend class Allocation;
virtual void deallocate(size_t offset);
LIBBINDER_EXPORTED const sp<IMemoryHeap>& heap() const;
SimpleBestFitAllocator* allocator() const;
sp<IMemoryHeap> mHeap;
SimpleBestFitAllocator* mAllocator;
};
// ----------------------------------------------------------------------------
} // namespace android
@@ -1,111 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <binder/Common.h>
#include <binder/IMemory.h>
namespace android {
// ---------------------------------------------------------------------------
class MemoryHeapBase : public BnMemoryHeap {
public:
static constexpr auto MEMFD_ALLOW_SEALING_FLAG = 0x00000800;
enum {
READ_ONLY = IMemoryHeap::READ_ONLY,
// memory won't be mapped locally, but will be mapped in the remote
// process.
DONT_MAP_LOCALLY = 0x00000100,
NO_CACHING = 0x00000200,
// Bypass ashmem-libcutils to create a memfd shared region.
// Ashmem-libcutils will eventually migrate to memfd.
// Memfd has security benefits and supports file sealing.
// Calling process will need to modify selinux permissions to
// open access to tmpfs files. See audioserver for examples.
// This is only valid for size constructor.
// For host compilation targets, memfd is stubbed in favor of /tmp
// files so sealing is not enforced.
FORCE_MEMFD = 0x00000400,
// Default opt-out of sealing behavior in memfd to avoid potential DOS.
// Clients of shared files can seal at anytime via syscall, leading to
// TOC/TOU issues if additional seals prevent access from the creating
// process. Alternatively, seccomp fcntl().
MEMFD_ALLOW_SEALING = FORCE_MEMFD | MEMFD_ALLOW_SEALING_FLAG
};
/*
* maps the memory referenced by fd. but DOESN'T take ownership
* of the filedescriptor (it makes a copy with dup()
*/
LIBBINDER_EXPORTED MemoryHeapBase(int fd, size_t size, uint32_t flags = 0, off_t offset = 0);
/*
* maps memory from the given device
*/
LIBBINDER_EXPORTED explicit MemoryHeapBase(const char* device, size_t size = 0,
uint32_t flags = 0);
/*
* maps memory from ashmem, with the given name for debugging
* if the READ_ONLY flag is set, the memory will be writeable by the calling process,
* but not by others. this is NOT the case with the other ctors.
*/
LIBBINDER_EXPORTED explicit MemoryHeapBase(size_t size, uint32_t flags = 0,
char const* name = nullptr);
LIBBINDER_EXPORTED virtual ~MemoryHeapBase();
/* implement IMemoryHeap interface */
LIBBINDER_EXPORTED int getHeapID() const override;
/* virtual address of the heap. returns MAP_FAILED in case of error */
LIBBINDER_EXPORTED void* getBase() const override;
LIBBINDER_EXPORTED size_t getSize() const override;
LIBBINDER_EXPORTED uint32_t getFlags() const override;
LIBBINDER_EXPORTED off_t getOffset() const override;
LIBBINDER_EXPORTED const char* getDevice() const;
/* this closes this heap -- use carefully */
LIBBINDER_EXPORTED void dispose();
protected:
LIBBINDER_EXPORTED MemoryHeapBase();
// init() takes ownership of fd
LIBBINDER_EXPORTED status_t init(int fd, void* base, size_t size, int flags = 0,
const char* device = nullptr);
private:
status_t mapfd(int fd, bool writeableByCaller, size_t size, off_t offset = 0);
int mFD;
size_t mSize;
void* mBase;
uint32_t mFlags;
const char* mDevice;
bool mNeedUnmap;
off_t mOffset;
};
// ---------------------------------------------------------------------------
} // namespace android
+9 -55
View File
@@ -26,9 +26,6 @@
#include <vector> #include <vector>
#include <binder/unique_fd.h> #include <binder/unique_fd.h>
#ifndef BINDER_DISABLE_NATIVE_HANDLE
#include <cutils/native_handle.h>
#endif
#include <utils/Errors.h> #include <utils/Errors.h>
#include <utils/RefBase.h> #include <utils/RefBase.h>
#include <utils/String16.h> #include <utils/String16.h>
@@ -51,7 +48,6 @@ template <typename T> class LightFlattenable;
class IBinder; class IBinder;
class IPCThreadState; class IPCThreadState;
class ProcessState; class ProcessState;
class RpcSession;
class String8; class String8;
class TextOutput; class TextOutput;
namespace binder { namespace binder {
@@ -63,7 +59,6 @@ class RecordedTransaction;
class Parcel { class Parcel {
friend class IPCThreadState; friend class IPCThreadState;
friend class RpcState;
public: public:
class ReadableBlob; class ReadableBlob;
@@ -126,10 +121,6 @@ public:
// is for an RPC transaction). // is for an RPC transaction).
LIBBINDER_EXPORTED void markForBinder(const sp<IBinder>& binder); LIBBINDER_EXPORTED void markForBinder(const sp<IBinder>& binder);
// Whenever possible, markForBinder should be preferred. This method is
// called automatically on reply Parcels for RPC transactions.
LIBBINDER_EXPORTED void markForRpc(const sp<RpcSession>& session);
// Whether this Parcel is written for RPC transactions (after calls to // Whether this Parcel is written for RPC transactions (after calls to
// markForBinder or markForRpc). // markForBinder or markForRpc).
LIBBINDER_EXPORTED bool isForRpc() const; LIBBINDER_EXPORTED bool isForRpc() const;
@@ -347,14 +338,6 @@ public:
template<typename T> template<typename T>
status_t writeVectorSize(const std::unique_ptr<std::vector<T>>& val) __attribute__((deprecated("use std::optional version instead"))); status_t writeVectorSize(const std::unique_ptr<std::vector<T>>& val) __attribute__((deprecated("use std::optional version instead")));
#ifndef BINDER_DISABLE_NATIVE_HANDLE
// Place a native_handle into the parcel (the native_handle's file-
// descriptors are dup'ed, so it is safe to delete the native_handle
// when this function returns).
// Doesn't take ownership of the native_handle.
LIBBINDER_EXPORTED status_t writeNativeHandle(const native_handle* handle);
#endif
// Place a file descriptor into the parcel. The given fd must remain // Place a file descriptor into the parcel. The given fd must remain
// valid for the lifetime of the parcel. // valid for the lifetime of the parcel.
// The Parcel does not take ownership of the given fd unless you ask it to. // The Parcel does not take ownership of the given fd unless you ask it to.
@@ -601,14 +584,6 @@ public:
// response headers rather than doing it by hand. // response headers rather than doing it by hand.
LIBBINDER_EXPORTED int32_t readExceptionCode() const; LIBBINDER_EXPORTED int32_t readExceptionCode() const;
#ifndef BINDER_DISABLE_NATIVE_HANDLE
// Retrieve native_handle from the parcel. This returns a copy of the
// parcel's native_handle (the caller takes ownership). The caller
// must free the native_handle with native_handle_close() and
// native_handle_delete().
LIBBINDER_EXPORTED native_handle* readNativeHandle() const;
#endif
// Retrieve a file descriptor from the parcel. This returns the raw fd // Retrieve a file descriptor from the parcel. This returns the raw fd
// in the parcel, which you do not own -- use dup() to get your own copy. // in the parcel, which you do not own -- use dup() to get your own copy.
LIBBINDER_EXPORTED int readFileDescriptor() const; LIBBINDER_EXPORTED int readFileDescriptor() const;
@@ -651,6 +626,11 @@ public:
LIBBINDER_EXPORTED void print(std::ostream& to, uint32_t flags = 0) const; LIBBINDER_EXPORTED void print(std::ostream& to, uint32_t flags = 0) const;
// This API is to quickly become a view of another Parcel, so that we can also
// test 'owner' paths quickly. It's extremely dangerous to use this API in
// practice, and you should never ever do it.
LIBBINDER_EXPORTED void makeDangerousViewOf(Parcel* p);
private: private:
// Close all file descriptors in the parcel at object positions >= newObjectsSize. // Close all file descriptors in the parcel at object positions >= newObjectsSize.
void closeFileDescriptors(size_t newObjectsSize); void closeFileDescriptors(size_t newObjectsSize);
@@ -665,16 +645,10 @@ private:
size_t ipcObjectsCount() const; size_t ipcObjectsCount() const;
void ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects, void ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
size_t objectsCount, release_func relFunc); size_t objectsCount, release_func relFunc);
// Takes ownership even when an error is returned.
status_t rpcSetDataReference(
const sp<RpcSession>& session, const uint8_t* data, size_t dataSize,
const uint32_t* objectTable, size_t objectTableSize,
std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds,
release_func relFunc);
status_t finishWrite(size_t len); status_t finishWrite(size_t len);
void releaseObjects(); void releaseObjects();
void acquireObjects(); void reacquireObjects(size_t objectSize);
status_t growData(size_t len); status_t growData(size_t len);
// Clear the Parcel and set the capacity to `desired`. // Clear the Parcel and set the capacity to `desired`.
// Doesn't reset the RPC session association. // Doesn't reset the RPC session association.
@@ -1345,30 +1319,10 @@ private:
mutable bool mFdsKnown = true; mutable bool mFdsKnown = true;
mutable bool mHasFds = false; mutable bool mHasFds = false;
}; };
// Fields only needed when parcelling for RPC Binder.
struct RpcFields { // TrickyStoreOSS stub
RpcFields(const sp<RpcSession>& session); struct RpcFields {};
// Should always be non-null.
const sp<RpcSession> mSession;
enum ObjectType : int32_t {
TYPE_BINDER_NULL = 0,
TYPE_BINDER = 1,
// FD to be passed via native transport (Trusty IPC or UNIX domain socket).
TYPE_NATIVE_FILE_DESCRIPTOR = 2,
};
// Sorted.
std::vector<uint32_t> mObjectPositions;
// File descriptors referenced by the parcel data. Should be indexed
// using the offsets in the parcel data. Don't assume the list is in the
// same order as `mObjectPositions`.
//
// Boxed to save space. Lazy allocated.
std::unique_ptr<std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>> mFds;
};
std::variant<KernelFields, RpcFields> mVariantFields; std::variant<KernelFields, RpcFields> mVariantFields;
// Pointer to KernelFields in mVariantFields if present. // Pointer to KernelFields in mVariantFields if present.
@@ -1,70 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
#include <binder/unique_fd.h>
namespace android {
namespace os {
/*
* C++ implementation of the Java class android.os.ParcelFileDescriptor
*/
class LIBBINDER_EXPORTED ParcelFileDescriptor : public android::Parcelable {
public:
ParcelFileDescriptor();
explicit ParcelFileDescriptor(binder::unique_fd fd);
ParcelFileDescriptor(ParcelFileDescriptor&& other) noexcept : mFd(std::move(other.mFd)) { }
ParcelFileDescriptor& operator=(ParcelFileDescriptor&& other) noexcept = default;
~ParcelFileDescriptor() override;
int get() const { return mFd.get(); }
binder::unique_fd release() { return std::move(mFd); }
void reset(binder::unique_fd fd = binder::unique_fd()) { mFd = std::move(fd); }
// android::Parcelable override:
android::status_t writeToParcel(android::Parcel* parcel) const override;
android::status_t readFromParcel(const android::Parcel* parcel) override;
inline std::string toString() const { return "ParcelFileDescriptor:" + std::to_string(get()); }
inline bool operator!=(const ParcelFileDescriptor& rhs) const {
return mFd.get() != rhs.mFd.get();
}
inline bool operator<(const ParcelFileDescriptor& rhs) const {
return mFd.get() < rhs.mFd.get();
}
inline bool operator<=(const ParcelFileDescriptor& rhs) const {
return mFd.get() <= rhs.mFd.get();
}
inline bool operator==(const ParcelFileDescriptor& rhs) const {
return mFd.get() == rhs.mFd.get();
}
inline bool operator>(const ParcelFileDescriptor& rhs) const {
return mFd.get() > rhs.mFd.get();
}
inline bool operator>=(const ParcelFileDescriptor& rhs) const {
return mFd.get() >= rhs.mFd.get();
}
private:
binder::unique_fd mFd;
};
} // namespace os
} // namespace android
@@ -1,146 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
#include <utils/String16.h>
#include <mutex>
#include <optional>
#include <tuple>
namespace android {
namespace os {
/*
* C++ implementation of the Java class android.os.ParcelableHolder
*/
class LIBBINDER_EXPORTED ParcelableHolder : public android::Parcelable {
public:
ParcelableHolder() = delete;
explicit ParcelableHolder(Stability stability) : mStability(stability){}
virtual ~ParcelableHolder() = default;
ParcelableHolder(const ParcelableHolder& other) {
mParcelable = other.mParcelable;
mParcelableName = other.mParcelableName;
if (other.mParcelPtr) {
mParcelPtr = std::make_unique<Parcel>();
mParcelPtr->appendFrom(other.mParcelPtr.get(), 0, other.mParcelPtr->dataSize());
}
mStability = other.mStability;
}
ParcelableHolder(ParcelableHolder&& other) = default;
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
void reset() {
this->mParcelable = nullptr;
this->mParcelableName = std::nullopt;
this->mParcelPtr = nullptr;
}
template <typename T>
status_t setParcelable(T&& p) {
using Tt = typename std::decay<T>::type;
return setParcelable<Tt>(std::make_shared<Tt>(std::forward<T>(p)));
}
template <typename T>
status_t setParcelable(std::shared_ptr<T> p) {
static_assert(std::is_base_of<Parcelable, T>::value, "T must be derived from Parcelable");
if (p && this->getStability() > p->getStability()) {
return android::BAD_VALUE;
}
this->mParcelable = p;
this->mParcelableName = T::getParcelableDescriptor();
this->mParcelPtr = nullptr;
return android::OK;
}
template <typename T>
status_t getParcelable(std::shared_ptr<T>* ret) const {
static_assert(std::is_base_of<Parcelable, T>::value, "T must be derived from Parcelable");
const String16& parcelableDesc = T::getParcelableDescriptor();
if (!this->mParcelPtr) {
if (!this->mParcelable || !this->mParcelableName) {
ALOGD("empty ParcelableHolder");
*ret = nullptr;
return android::OK;
} else if (parcelableDesc != *mParcelableName) {
ALOGD("extension class name mismatch expected:%s actual:%s",
String8(*mParcelableName).c_str(), String8(parcelableDesc).c_str());
*ret = nullptr;
return android::BAD_VALUE;
}
*ret = std::static_pointer_cast<T>(mParcelable);
return android::OK;
}
this->mParcelPtr->setDataPosition(0);
status_t status = this->mParcelPtr->readString16(&this->mParcelableName);
if (status != android::OK || parcelableDesc != this->mParcelableName) {
this->mParcelableName = std::nullopt;
*ret = nullptr;
return status;
}
this->mParcelable = std::make_shared<T>();
status = mParcelable.get()->readFromParcel(this->mParcelPtr.get());
if (status != android::OK) {
this->mParcelableName = std::nullopt;
this->mParcelable = nullptr;
*ret = nullptr;
return status;
}
this->mParcelPtr = nullptr;
*ret = std::static_pointer_cast<T>(mParcelable);
return android::OK;
}
Stability getStability() const override { return mStability; }
inline std::string toString() const {
return "ParcelableHolder:" +
(mParcelableName ? std::string(String8(mParcelableName.value()).c_str())
: "<parceled>");
}
inline bool operator!=(const ParcelableHolder& rhs) const {
return this != &rhs;
}
inline bool operator<(const ParcelableHolder& rhs) const {
return this < &rhs;
}
inline bool operator<=(const ParcelableHolder& rhs) const {
return this <= &rhs;
}
inline bool operator==(const ParcelableHolder& rhs) const {
return this == &rhs;
}
inline bool operator>(const ParcelableHolder& rhs) const {
return this > &rhs;
}
inline bool operator>=(const ParcelableHolder& rhs) const {
return this >= &rhs;
}
private:
mutable std::shared_ptr<Parcelable> mParcelable;
mutable std::optional<String16> mParcelableName;
mutable std::unique_ptr<Parcel> mParcelPtr;
Stability mStability;
};
} // namespace os
} // namespace android
@@ -1,86 +0,0 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifndef __ANDROID_VNDK__
#include <stdint.h>
#include <unistd.h>
#include <utils/String16.h>
#include <utils/Singleton.h>
#include <utils/SortedVector.h>
#include <binder/Common.h>
namespace android {
// ---------------------------------------------------------------------------
/*
* PermissionCache caches permission checks for a given uid.
*
* Currently the cache is not updated when there is a permission change,
* for instance when an application is uninstalled.
*
* IMPORTANT: for the reason stated above, only system permissions are safe
* to cache. This restriction may be lifted at a later time.
*
*/
class PermissionCache : Singleton<PermissionCache> {
struct Entry {
String16 name;
uid_t uid;
bool granted;
inline bool operator < (const Entry& e) const {
return (uid == e.uid) ? (name < e.name) : (uid < e.uid);
}
};
mutable Mutex mLock;
// we pool all the permission names we see, as many permissions checks
// will have identical names
SortedVector< String16 > mPermissionNamesPool;
// this is our cache per say. it stores pooled names.
SortedVector< Entry > mCache;
// free the whole cache, but keep the permission name pool
void purge();
status_t check(bool* granted,
const String16& permission, uid_t uid) const;
void cache(const String16& permission, uid_t uid, bool granted);
public:
LIBBINDER_EXPORTED PermissionCache();
LIBBINDER_EXPORTED static bool checkCallingPermission(const String16& permission);
LIBBINDER_EXPORTED static bool checkCallingPermission(const String16& permission,
int32_t* outPid, int32_t* outUid);
LIBBINDER_EXPORTED static bool checkPermission(const String16& permission, pid_t pid,
uid_t uid);
LIBBINDER_EXPORTED static void purgeCache();
};
// ---------------------------------------------------------------------------
} // namespace android
#else // __ANDROID_VNDK__
#error "This header is not visible to vendors"
#endif // __ANDROID_VNDK__
@@ -1,65 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifndef __ANDROID_VNDK__
#include <binder/Common.h>
#include <binder/IPermissionController.h>
#include <utils/Mutex.h>
// ---------------------------------------------------------------------------
namespace android {
class PermissionController {
public:
enum {
MATCH_SYSTEM_ONLY = 1<<16,
MATCH_UNINSTALLED_PACKAGES = 1<<13,
MATCH_FACTORY_ONLY = 1<<21,
MATCH_INSTANT = 1<<23
};
enum {
MODE_ALLOWED = 0,
MODE_IGNORED = 1,
MODE_ERRORED = 2,
MODE_DEFAULT = 3,
};
LIBBINDER_EXPORTED PermissionController();
LIBBINDER_EXPORTED bool checkPermission(const String16& permission, int32_t pid, int32_t uid);
LIBBINDER_EXPORTED int32_t noteOp(const String16& op, int32_t uid, const String16& packageName);
LIBBINDER_EXPORTED void getPackagesForUid(const uid_t uid, Vector<String16>& packages);
LIBBINDER_EXPORTED bool isRuntimePermission(const String16& permission);
LIBBINDER_EXPORTED int getPackageUid(const String16& package, int flags);
private:
Mutex mLock;
sp<IPermissionController> mService;
sp<IPermissionController> getService();
};
} // namespace android
// ---------------------------------------------------------------------------
#else // __ANDROID_VNDK__
#error "This header is not visible to vendors"
#endif // __ANDROID_VNDK__
@@ -1,130 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <map>
#include <set>
#include <vector>
#include <binder/Common.h>
#include <binder/Parcelable.h>
#include <utils/String16.h>
#include <utils/StrongPointer.h>
namespace android {
namespace os {
/*
* C++ implementation of PersistableBundle, a mapping from String values to
* various types that can be saved to persistent and later restored.
*/
class LIBBINDER_EXPORTED PersistableBundle : public Parcelable {
public:
PersistableBundle() = default;
virtual ~PersistableBundle() = default;
PersistableBundle(const PersistableBundle& bundle) = default;
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
bool empty() const;
size_t size() const;
size_t erase(const String16& key);
/*
* Setters for PersistableBundle. Adds a a key-value pair instantiated with
* |key| and |value| into the member map appropriate for the type of |value|.
* If there is already an existing value for |key|, |value| will replace it.
*/
void putBoolean(const String16& key, bool value);
void putInt(const String16& key, int32_t value);
void putLong(const String16& key, int64_t value);
void putDouble(const String16& key, double value);
void putString(const String16& key, const String16& value);
void putBooleanVector(const String16& key, const std::vector<bool>& value);
void putIntVector(const String16& key, const std::vector<int32_t>& value);
void putLongVector(const String16& key, const std::vector<int64_t>& value);
void putDoubleVector(const String16& key, const std::vector<double>& value);
void putStringVector(const String16& key, const std::vector<String16>& value);
void putPersistableBundle(const String16& key, const PersistableBundle& value);
/*
* Getters for PersistableBundle. If |key| exists, these methods write the
* value associated with |key| into |out|, and return true. Otherwise, these
* methods return false.
*/
bool getBoolean(const String16& key, bool* out) const;
bool getInt(const String16& key, int32_t* out) const;
bool getLong(const String16& key, int64_t* out) const;
bool getDouble(const String16& key, double* out) const;
bool getString(const String16& key, String16* out) const;
bool getBooleanVector(const String16& key, std::vector<bool>* out) const;
bool getIntVector(const String16& key, std::vector<int32_t>* out) const;
bool getLongVector(const String16& key, std::vector<int64_t>* out) const;
bool getDoubleVector(const String16& key, std::vector<double>* out) const;
bool getStringVector(const String16& key, std::vector<String16>* out) const;
bool getPersistableBundle(const String16& key, PersistableBundle* out) const;
/* Getters for all keys for each value type */
std::set<String16> getBooleanKeys() const;
std::set<String16> getIntKeys() const;
std::set<String16> getLongKeys() const;
std::set<String16> getDoubleKeys() const;
std::set<String16> getStringKeys() const;
std::set<String16> getBooleanVectorKeys() const;
std::set<String16> getIntVectorKeys() const;
std::set<String16> getLongVectorKeys() const;
std::set<String16> getDoubleVectorKeys() const;
std::set<String16> getStringVectorKeys() const;
std::set<String16> getPersistableBundleKeys() const;
friend bool operator==(const PersistableBundle& lhs, const PersistableBundle& rhs) {
return (lhs.mBoolMap == rhs.mBoolMap && lhs.mIntMap == rhs.mIntMap &&
lhs.mLongMap == rhs.mLongMap && lhs.mDoubleMap == rhs.mDoubleMap &&
lhs.mStringMap == rhs.mStringMap && lhs.mBoolVectorMap == rhs.mBoolVectorMap &&
lhs.mIntVectorMap == rhs.mIntVectorMap &&
lhs.mLongVectorMap == rhs.mLongVectorMap &&
lhs.mDoubleVectorMap == rhs.mDoubleVectorMap &&
lhs.mStringVectorMap == rhs.mStringVectorMap &&
lhs.mPersistableBundleMap == rhs.mPersistableBundleMap);
}
friend bool operator!=(const PersistableBundle& lhs, const PersistableBundle& rhs) {
return !(lhs == rhs);
}
private:
status_t writeToParcelInner(Parcel* parcel) const;
status_t readFromParcelInner(const Parcel* parcel, size_t length);
std::map<String16, bool> mBoolMap;
std::map<String16, int32_t> mIntMap;
std::map<String16, int64_t> mLongMap;
std::map<String16, double> mDoubleMap;
std::map<String16, String16> mStringMap;
std::map<String16, std::vector<bool>> mBoolVectorMap;
std::map<String16, std::vector<int32_t>> mIntVectorMap;
std::map<String16, std::vector<int64_t>> mLongVectorMap;
std::map<String16, std::vector<double>> mDoubleVectorMap;
std::map<String16, std::vector<String16>> mStringVectorMap;
std::map<String16, PersistableBundle> mPersistableBundleMap;
};
} // namespace os
} // namespace android
@@ -1,89 +0,0 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/Parcel.h>
#include <binder/unique_fd.h>
#include <mutex>
namespace android {
namespace binder::debug {
// Warning: Transactions are sequentially recorded to the file descriptor in a
// non-stable format. A detailed description of the recording format can be found in
// RecordedTransaction.cpp.
class RecordedTransaction {
public:
// Filled with the first transaction from fd.
LIBBINDER_EXPORTED static std::optional<RecordedTransaction> fromFile(
const binder::unique_fd& fd);
// Filled with the arguments.
LIBBINDER_EXPORTED static std::optional<RecordedTransaction> fromDetails(
const String16& interfaceName, uint32_t code, uint32_t flags, timespec timestamp,
const Parcel& data, const Parcel& reply, status_t err);
LIBBINDER_EXPORTED RecordedTransaction(RecordedTransaction&& t) noexcept;
[[nodiscard]] LIBBINDER_EXPORTED status_t dumpToFile(const binder::unique_fd& fd) const;
LIBBINDER_EXPORTED const std::string& getInterfaceName() const;
LIBBINDER_EXPORTED uint32_t getCode() const;
LIBBINDER_EXPORTED uint32_t getFlags() const;
LIBBINDER_EXPORTED int32_t getReturnedStatus() const;
LIBBINDER_EXPORTED timespec getTimestamp() const;
LIBBINDER_EXPORTED uint32_t getVersion() const;
LIBBINDER_EXPORTED const Parcel& getDataParcel() const;
LIBBINDER_EXPORTED const Parcel& getReplyParcel() const;
LIBBINDER_EXPORTED const std::vector<uint64_t>& getObjectOffsets() const;
private:
RecordedTransaction() = default;
android::status_t writeChunk(const binder::borrowed_fd, uint32_t chunkType, size_t byteCount,
const uint8_t* data) const;
#pragma clang diagnostic push
#pragma clang diagnostic error "-Wpadded"
struct TransactionHeader {
uint32_t code = 0;
uint32_t flags = 0;
int32_t statusReturned = 0;
uint32_t version = 0; // !0 iff Rpc
int64_t timestampSeconds = 0;
int32_t timestampNanoseconds = 0;
int32_t reserved = 0;
};
#pragma clang diagnostic pop
static_assert(sizeof(TransactionHeader) == 32);
static_assert(sizeof(TransactionHeader) % 8 == 0);
struct MovableData { // movable
TransactionHeader mHeader;
std::string mInterfaceName;
std::vector<uint64_t> mSentObjectData; /* Object Offsets */
};
MovableData mData;
Parcel mSentDataOnly;
Parcel mReplyDataOnly;
};
} // namespace binder::debug
} // namespace android
@@ -1,41 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Formats for serializing TLS certificate.
#pragma once
#include <string>
namespace android {
enum class RpcCertificateFormat {
PEM,
DER,
};
static inline std::string PrintToString(RpcCertificateFormat format) {
switch (format) {
case RpcCertificateFormat::PEM:
return "PEM";
case RpcCertificateFormat::DER:
return "DER";
default:
return "<unknown>";
}
}
} // namespace android
@@ -1,41 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Formats for serializing TLS private keys.
#pragma once
#include <string>
namespace android {
enum class RpcKeyFormat {
PEM,
DER,
};
static inline std::string PrintToString(RpcKeyFormat format) {
switch (format) {
case RpcKeyFormat::PEM:
return "PEM";
case RpcKeyFormat::DER:
return "DER";
default:
return "<unknown>";
}
}
} // namespace android
@@ -1,297 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#include <binder/RpcSession.h>
#include <binder/RpcThreads.h>
#include <binder/RpcTransport.h>
#include <binder/unique_fd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <bitset>
#include <mutex>
#include <thread>
namespace android {
class FdTrigger;
class RpcServerTrusty;
class RpcSocketAddress;
/**
* This represents a server of an interface, which may be connected to by any
* number of clients over sockets.
*
* Usage:
* auto server = RpcServer::make();
* // only supports one now
* if (!server->setup*Server(...)) {
* :(
* }
* server->join();
*/
class RpcServer final : public virtual RefBase, private RpcSession::EventListener {
public:
LIBBINDER_EXPORTED static sp<RpcServer> make(
std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory = nullptr);
/**
* Creates an RPC server that bootstraps sessions using an existing
* Unix domain socket pair.
*
* Callers should create a pair of SOCK_STREAM Unix domain sockets, pass
* one to RpcServer::setupUnixDomainSocketBootstrapServer and the other
* to RpcSession::setupUnixDomainSocketBootstrapClient. Multiple client
* session can be created from the client end of the pair.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t
setupUnixDomainSocketBootstrapServer(binder::unique_fd serverFd);
/**
* This represents a session for responses, e.g.:
*
* process A serves binder a
* process B opens a session to process A
* process B makes binder b and sends it to A
* A uses this 'back session' to send things back to B
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupUnixDomainServer(const char* path);
/**
* Sets up an RPC server with a raw socket file descriptor.
* The socket should be created and bound to a socket address already, e.g.
* the socket can be created in init.rc.
*
* This method is used in the libbinder_rpc_unstable API
* RunInitUnixDomainRpcServer().
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupRawSocketServer(binder::unique_fd socket_fd);
/**
* Creates an RPC server binding to the given CID at the given port.
*
* Set |port| to VMADDR_PORT_ANY to pick an ephemeral port. In this case, |assignedPort|
* will be set to the picked port number, if it is not null.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockServer(unsigned bindCid, unsigned port,
unsigned* assignedPort = nullptr);
/**
* Creates an RPC server at the current port using IPv4.
*
* TODO(b/182914638): IPv6 support
*
* Set |port| to 0 to pick an ephemeral port; see discussion of
* /proc/sys/net/ipv4/ip_local_port_range in ip(7). In this case, |assignedPort|
* will be set to the picked port number, if it is not null.
*
* Set the IPv4 address for the socket to be listening on.
* "127.0.0.1" allows for local connections from the same device.
* "0.0.0.0" allows for connections on any IP address that the device may
* have
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupInetServer(const char* address,
unsigned int port,
unsigned int* assignedPort = nullptr);
/**
* If setup*Server has been successful, return true. Otherwise return false.
*/
[[nodiscard]] LIBBINDER_EXPORTED bool hasServer();
/**
* If hasServer(), return the server FD. Otherwise return invalid FD.
*/
[[nodiscard]] LIBBINDER_EXPORTED binder::unique_fd releaseServer();
/**
* Set up server using an external FD previously set up by releaseServer().
* Return false if there's already a server.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupExternalServer(binder::unique_fd serverFd);
/**
* This must be called before adding a client session. This corresponds
* to the number of incoming connections to RpcSession objects in the
* server, which will correspond to the number of outgoing connections
* in client RpcSession objects.
*
* If this is not specified, this will be a single-threaded server.
*
* TODO(b/167966510): these are currently created per client, but these
* should be shared.
*/
LIBBINDER_EXPORTED void setMaxThreads(size_t threads);
LIBBINDER_EXPORTED size_t getMaxThreads();
/**
* By default, the latest protocol version which is supported by a client is
* used. However, this can be used in order to prevent newer protocol
* versions from ever being used. This is expected to be useful for testing.
*/
[[nodiscard]] LIBBINDER_EXPORTED bool setProtocolVersion(uint32_t version);
/**
* Set the supported transports for sending and receiving file descriptors.
*
* Clients will propose a mode when connecting. If the mode is not in the
* provided list, the connection will be rejected.
*/
LIBBINDER_EXPORTED void setSupportedFileDescriptorTransportModes(
const std::vector<RpcSession::FileDescriptorTransportMode>& modes);
/**
* The root object can be retrieved by any client, without any
* authentication. TODO(b/183988761)
*
* Holds a strong reference to the root object.
*/
LIBBINDER_EXPORTED void setRootObject(const sp<IBinder>& binder);
/**
* Holds a weak reference to the root object.
*/
LIBBINDER_EXPORTED void setRootObjectWeak(const wp<IBinder>& binder);
/**
* Allows a root object to be created for each session.
*
* Takes one argument: a callable that is invoked once per new session.
* The callable takes three arguments:
* - a weak pointer to the session. If you want to hold onto this in the root object, then
* you should keep a weak pointer, and promote it when needed. For instance, if you refer
* to this from the root object, then you could get ahold of transport-specific information.
* - a type-erased pointer to an OS- and transport-specific address structure, e.g.,
* sockaddr_vm for vsock
* - an integer representing the size in bytes of that structure. The callable should
* validate the size, then cast the type-erased pointer to a pointer to the actual type of the
* address, e.g., const void* to const sockaddr_vm*.
*/
LIBBINDER_EXPORTED void setPerSessionRootObject(
std::function<sp<IBinder>(wp<RpcSession> session, const void*, size_t)>&& object);
LIBBINDER_EXPORTED sp<IBinder> getRootObject();
/**
* Set optional filter of incoming connections based on the peer's address.
*
* Takes one argument: a callable that is invoked on each accept()-ed
* connection and returns false if the connection should be dropped.
* See the description of setPerSessionRootObject() for details about
* the callable's arguments.
*/
LIBBINDER_EXPORTED void setConnectionFilter(std::function<bool(const void*, size_t)>&& filter);
/**
* Set optional modifier of each newly created server socket.
*
* The only argument is a successfully created file descriptor, not bound to an address yet.
*/
LIBBINDER_EXPORTED void setServerSocketModifier(
std::function<void(binder::borrowed_fd)>&& modifier);
/**
* See RpcTransportCtx::getCertificate
*/
LIBBINDER_EXPORTED std::vector<uint8_t> getCertificate(RpcCertificateFormat);
/**
* Runs join() in a background thread. Immediately returns.
*/
LIBBINDER_EXPORTED void start();
/**
* You must have at least one client session before calling this.
*
* If a client needs to actively terminate join, call shutdown() in a separate thread.
*
* At any given point, there can only be one thread calling join().
*
* Warning: if shutdown is called, this will return while the shutdown is
* still occurring. To ensure that the service is fully shutdown, you might
* want to call shutdown after 'join' returns.
*/
LIBBINDER_EXPORTED void join();
/**
* Shut down any existing join(). Return true if successfully shut down, false otherwise
* (e.g. no join() is running). Will wait for the server to be fully
* shutdown.
*
* Warning: this will hang if it is called from its own thread.
*/
[[nodiscard]] LIBBINDER_EXPORTED bool shutdown();
/**
* For debugging!
*/
LIBBINDER_EXPORTED std::vector<sp<RpcSession>> listSessions();
LIBBINDER_EXPORTED size_t numUninitializedSessions();
/**
* Whether any requests are currently being processed.
*/
LIBBINDER_EXPORTED bool hasActiveRequests();
LIBBINDER_EXPORTED ~RpcServer();
private:
friend RpcServerTrusty;
friend sp<RpcServer>;
explicit RpcServer(std::unique_ptr<RpcTransportCtx> ctx);
void onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) override;
void onSessionIncomingThreadEnded() override;
status_t setupExternalServer(
binder::unique_fd serverFd,
std::function<status_t(const RpcServer&, RpcTransportFd*)>&& acceptFn);
static constexpr size_t kRpcAddressSize = 128;
static void establishConnection(
sp<RpcServer>&& server, RpcTransportFd clientFd,
std::array<uint8_t, kRpcAddressSize> addr, size_t addrLen,
std::function<void(sp<RpcSession>&&, RpcSession::PreJoinSetupResult&&)>&& joinFn);
static status_t acceptSocketConnection(const RpcServer& server, RpcTransportFd* out);
static status_t recvmsgSocketConnection(const RpcServer& server, RpcTransportFd* out);
[[nodiscard]] status_t setupSocketServer(const RpcSocketAddress& address);
const std::unique_ptr<RpcTransportCtx> mCtx;
size_t mMaxThreads = 1;
std::optional<uint32_t> mProtocolVersion;
// A mode is supported if the N'th bit is on, where N is the mode enum's value.
std::bitset<8> mSupportedFileDescriptorTransportModes = std::bitset<8>().set(
static_cast<size_t>(RpcSession::FileDescriptorTransportMode::NONE));
RpcTransportFd mServer; // socket we are accepting sessions on
RpcMutex mLock; // for below
std::unique_ptr<RpcMaybeThread> mJoinThread;
bool mJoinThreadRunning = false;
std::map<RpcMaybeThread::id, RpcMaybeThread> mConnectingThreads;
sp<IBinder> mRootObject;
wp<IBinder> mRootObjectWeak;
std::function<sp<IBinder>(wp<RpcSession>, const void*, size_t)> mRootObjectFactory;
std::function<bool(const void*, size_t)> mConnectionFilter;
std::function<void(binder::borrowed_fd)> mServerSocketModifier;
std::map<std::vector<uint8_t>, sp<RpcSession>> mSessions;
std::unique_ptr<FdTrigger> mShutdownTrigger;
RpcConditionVariable mShutdownCv;
std::function<status_t(const RpcServer& server, RpcTransportFd* out)> mAcceptFn;
};
} // namespace android
@@ -1,413 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#include <binder/RpcThreads.h>
#include <binder/RpcTransport.h>
#include <binder/unique_fd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <map>
#include <optional>
#include <type_traits>
#include <vector>
namespace android {
class Parcel;
class RpcServer;
class RpcServerTrusty;
class RpcSocketAddress;
class RpcState;
class RpcTransport;
class FdTrigger;
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 2;
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL = 0xF0000000;
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = 1;
// Starting with this version:
//
// * RpcWireReply is larger (4 bytes -> 20).
// * RpcWireTransaction and RpcWireReplyV1 include the parcel data size.
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE = 1;
/**
* This represents a session (group of connections) between a client
* and a server. Multiple connections are needed for multiple parallel "binder"
* calls which may also have nested calls.
*
* Once a binder exists in the session, if all references to all binders are dropped,
* the session shuts down.
*/
class RpcSession final : public virtual RefBase {
public:
// Create an RpcSession with default configuration (raw sockets).
LIBBINDER_EXPORTED static sp<RpcSession> make();
// Create an RpcSession with the given configuration. |serverRpcCertificateFormat| and
// |serverCertificate| must have values or be nullopt simultaneously. If they have values, set
// server certificate.
LIBBINDER_EXPORTED static sp<RpcSession> make(
std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory);
/**
* Set the maximum number of incoming threads allowed to be made (for things like callbacks).
* By default, this is 0. This must be called before setting up this connection as a client.
* Server sessions will inherits this value from RpcServer. Each thread will serve a
* connection to the remote RpcSession.
*
* If this is called, 'shutdown' on this session must also be called.
* Otherwise, a threadpool will leak.
*
* TODO(b/189955605): start these lazily - currently all are started
*/
LIBBINDER_EXPORTED void setMaxIncomingThreads(size_t threads);
LIBBINDER_EXPORTED size_t getMaxIncomingThreads();
/**
* Set the maximum number of outgoing connections allowed to be made.
* By default, this is |kDefaultMaxOutgoingConnections|. This must be called before setting up
* this connection as a client.
*
* For an RpcSession client, if you are connecting to a server which starts N threads,
* then this must be set to >= N. If you set the maximum number of outgoing connections
* to 1, but the server requests 10, then it would be considered an error. If you set a
* maximum number of connections to 10, and the server requests 1, then only 1 will be
* created. This API is used to limit the amount of resources a server can request you
* create.
*/
LIBBINDER_EXPORTED void setMaxOutgoingConnections(size_t connections);
LIBBINDER_EXPORTED size_t getMaxOutgoingThreads();
/**
* By default, the minimum of the supported versions of the client and the
* server will be used. Usually, this API should only be used for debugging.
*/
[[nodiscard]] LIBBINDER_EXPORTED bool setProtocolVersion(uint32_t version);
LIBBINDER_EXPORTED std::optional<uint32_t> getProtocolVersion();
enum class FileDescriptorTransportMode : uint8_t {
NONE = 0,
// Send file descriptors via unix domain socket ancillary data.
UNIX = 1,
// Send file descriptors as Trusty IPC handles.
TRUSTY = 2,
};
/**
* Set the transport for sending and receiving file descriptors.
*/
LIBBINDER_EXPORTED void setFileDescriptorTransportMode(FileDescriptorTransportMode mode);
LIBBINDER_EXPORTED FileDescriptorTransportMode getFileDescriptorTransportMode();
/**
* This should be called once per thread, matching 'join' in the remote
* process.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupUnixDomainClient(const char* path);
/**
* Connects to an RPC server over a nameless Unix domain socket pair.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t
setupUnixDomainSocketBootstrapClient(binder::unique_fd bootstrap);
/**
* Connects to an RPC server at the CID & port.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockClient(unsigned int cid, unsigned int port);
/**
* Connects to an RPC server at the given address and port.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupInetClient(const char* addr, unsigned int port);
/**
* Starts talking to an RPC server which has already been connected to. This
* is expected to be used when another process has permission to connect to
* a binder RPC service, but this process only has permission to talk to
* that service.
*
* For convenience, if 'fd' is -1, 'request' will be called.
*
* For future compatibility, 'request' should not reference any stack data.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t
setupPreconnectedClient(binder::unique_fd fd, std::function<binder::unique_fd()>&& request);
/**
* For debugging!
*
* Sets up an empty connection. All queries to this connection which require a
* response will never be satisfied. All data sent here will be
* unceremoniously cast down the bottomless pit, /dev/null.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t addNullDebuggingClient();
/**
* Query the other side of the session for the root object hosted by that
* process's RpcServer (if one exists)
*/
LIBBINDER_EXPORTED sp<IBinder> getRootObject();
/**
* Query the other side of the session for the maximum number of threads
* it supports (maximum number of concurrent non-nested synchronous transactions)
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t getRemoteMaxThreads(size_t* maxThreads);
/**
* See RpcTransportCtx::getCertificate
*/
LIBBINDER_EXPORTED std::vector<uint8_t> getCertificate(RpcCertificateFormat);
/**
* Shuts down the service.
*
* For client sessions, wait can be true or false. For server sessions,
* waiting is not currently supported (will abort).
*
* Warning: this is currently not active/nice (the server isn't told we're
* shutting down). Being nicer to the server could potentially make it
* reclaim resources faster.
*
* If this is called w/ 'wait' true, then this will wait for shutdown to
* complete before returning. This will hang if it is called from the
* session threadpool (when processing received calls).
*/
[[nodiscard]] LIBBINDER_EXPORTED bool shutdownAndWait(bool wait);
[[nodiscard]] LIBBINDER_EXPORTED status_t transact(const sp<IBinder>& binder, uint32_t code,
const Parcel& data, Parcel* reply,
uint32_t flags);
/**
* Generally, you should not call this, unless you are testing error
* conditions, as this is called automatically by BpBinders when they are
* deleted (this is also why a raw pointer is used here)
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t sendDecStrong(const BpBinder* binder);
/**
* Whether any requests are currently being processed.
*/
LIBBINDER_EXPORTED bool hasActiveRequests();
LIBBINDER_EXPORTED ~RpcSession();
/**
* Server if this session is created as part of a server (symmetrical to
* client servers). Otherwise, nullptr.
*/
LIBBINDER_EXPORTED sp<RpcServer> server();
// internal only
LIBBINDER_EXPORTED const std::unique_ptr<RpcState>& state() { return mRpcBinderState; }
/**
* Sets the session-specific root object. This is the object that will be used to attach
* the IAccessor binder to the RpcSession when a binder is set up via accessor.
*/
LIBBINDER_EXPORTED void setSessionSpecificRoot(const sp<IBinder>& sessionSpecificRoot);
private:
friend sp<RpcSession>;
friend RpcServer;
friend RpcServerTrusty;
friend RpcState;
explicit RpcSession(std::unique_ptr<RpcTransportCtx> ctx);
static constexpr size_t kDefaultMaxOutgoingConnections = 10;
// internal version of setProtocolVersion that
// optionally skips the mStartedSetup check
[[nodiscard]] bool setProtocolVersionInternal(uint32_t version, bool checkStarted);
// for 'target', see RpcState::sendDecStrongToTarget
[[nodiscard]] status_t sendDecStrongToTarget(uint64_t address, size_t target);
class EventListener : public virtual RefBase {
public:
virtual void onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) = 0;
virtual void onSessionIncomingThreadEnded() = 0;
};
class WaitForShutdownListener : public EventListener {
public:
void onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) override;
void onSessionIncomingThreadEnded() override;
void waitForShutdown(RpcMutexUniqueLock& lock, const sp<RpcSession>& session);
private:
RpcConditionVariable mCv;
std::atomic<size_t> mShutdownCount = 0;
};
friend WaitForShutdownListener;
struct RpcConnection : public RefBase {
std::unique_ptr<RpcTransport> rpcTransport;
// whether this or another thread is currently using this fd to make
// or receive transactions.
std::optional<uint64_t> exclusiveTid;
bool allowNested = false;
};
[[nodiscard]] status_t readId();
// A thread joining a server must always call these functions in order, and
// cleanup is only programmed once into join. These are in separate
// functions in order to allow for different locks to be taken during
// different parts of setup.
//
// transfer ownership of thread (usually done while a lock is taken on the
// structure which originally owns the thread)
void preJoinThreadOwnership(RpcMaybeThread thread);
// pass FD to thread and read initial connection information
struct PreJoinSetupResult {
// Server connection object associated with this
sp<RpcConnection> connection;
// Status of setup
status_t status;
};
PreJoinSetupResult preJoinSetup(std::unique_ptr<RpcTransport> rpcTransport);
// join on thread passed to preJoinThreadOwnership
static void join(sp<RpcSession>&& session, PreJoinSetupResult&& result);
// This is a workaround to support move-only functors.
// TODO: use std::move_only_function when it becomes available.
template <typename Fn,
// Fn must be a callable type taking (const std::vector<uint8_t>&, bool) and returning
// status_t
typename = std::enable_if_t<
std::is_invocable_r_v<status_t, Fn, const std::vector<uint8_t>&, bool>>>
[[nodiscard]] status_t setupClient(Fn&& connectAndInit);
[[nodiscard]] status_t setupSocketClient(const RpcSocketAddress& address);
[[nodiscard]] status_t setupOneSocketConnection(const RpcSocketAddress& address,
const std::vector<uint8_t>& sessionId,
bool incoming);
[[nodiscard]] status_t initAndAddConnection(RpcTransportFd fd,
const std::vector<uint8_t>& sessionId,
bool incoming);
[[nodiscard]] status_t addIncomingConnection(std::unique_ptr<RpcTransport> rpcTransport);
[[nodiscard]] status_t addOutgoingConnection(std::unique_ptr<RpcTransport> rpcTransport,
bool init);
[[nodiscard]] bool setForServer(const wp<RpcServer>& server,
const wp<RpcSession::EventListener>& eventListener,
const std::vector<uint8_t>& sessionId,
const sp<IBinder>& sessionSpecificRoot);
sp<RpcConnection> assignIncomingConnectionToThisThread(
std::unique_ptr<RpcTransport> rpcTransport);
[[nodiscard]] bool removeIncomingConnection(const sp<RpcConnection>& connection);
void clearConnectionTid(const sp<RpcConnection>& connection);
[[nodiscard]] status_t initShutdownTrigger();
/**
* Checks whether any connection is active (Not polling on fd)
*/
bool hasActiveConnection(const std::vector<sp<RpcConnection>>& connections);
enum class ConnectionUse {
CLIENT,
CLIENT_ASYNC,
CLIENT_REFCOUNT,
};
// Object representing exclusive access to a connection.
class ExclusiveConnection {
public:
[[nodiscard]] static status_t find(const sp<RpcSession>& session, ConnectionUse use,
ExclusiveConnection* connection);
~ExclusiveConnection();
const sp<RpcConnection>& get() { return mConnection; }
private:
static void findConnection(uint64_t tid, sp<RpcConnection>* exclusive,
sp<RpcConnection>* available,
std::vector<sp<RpcConnection>>& sockets,
size_t socketsIndexHint);
sp<RpcSession> mSession; // avoid deallocation
sp<RpcConnection> mConnection;
// whether this is being used for a nested transaction (being on the same
// thread guarantees we won't write in the middle of a message, the way
// the wire protocol is constructed guarantees this is safe).
bool mReentrant = false;
};
const std::unique_ptr<RpcTransportCtx> mCtx;
// On the other side of a session, for each of mOutgoing here, there should
// be one of mIncoming on the other side (and vice versa).
//
// For the simplest session, a single server with one client, you would
// have:
// - the server has a single 'mIncoming' and a thread listening on this
// - the client has a single 'mOutgoing' and makes calls to this
// - here, when the client makes a call, the server can call back into it
// (nested calls), but outside of this, the client will only ever read
// calls from the server when it makes a call itself.
//
// For a more complicated case, the client might itself open up a thread to
// serve calls to the server at all times (e.g. if it hosts a callback)
wp<RpcServer> mForServer; // maybe null, for client sessions
sp<WaitForShutdownListener> mShutdownListener; // used for client sessions
wp<EventListener> mEventListener; // mForServer if server, mShutdownListener if client
// session-specific root object (if a different root is used for each
// session)
sp<IBinder> mSessionSpecificRootObject;
std::vector<uint8_t> mId;
std::unique_ptr<FdTrigger> mShutdownTrigger;
std::unique_ptr<RpcState> mRpcBinderState;
RpcMutex mMutex; // for all below
bool mStartedSetup = false;
size_t mMaxIncomingThreads = 0;
size_t mMaxOutgoingConnections = kDefaultMaxOutgoingConnections;
std::optional<uint32_t> mProtocolVersion;
FileDescriptorTransportMode mFileDescriptorTransportMode = FileDescriptorTransportMode::NONE;
RpcConditionVariable mAvailableConnectionCv; // for mWaitingThreads
std::unique_ptr<RpcTransport> mBootstrapTransport;
struct ThreadState {
size_t mWaitingThreads = 0;
// hint index into clients, ++ when sending an async transaction
size_t mOutgoingOffset = 0;
std::vector<sp<RpcConnection>> mOutgoing;
// max size of mIncoming. Once any thread starts down, no more can be started.
size_t mMaxIncoming = 0;
std::vector<sp<RpcConnection>> mIncoming;
std::map<RpcMaybeThread::id, RpcMaybeThread> mThreads;
} mConnections;
};
} // namespace android
@@ -1,207 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Wraps the transport layer of RPC. Implementation may use plain sockets or TLS.
#pragma once
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include <utils/Errors.h>
#include <binder/Common.h>
#include <binder/Functional.h>
#include <binder/RpcCertificateFormat.h>
#include <binder/RpcThreads.h>
#include <binder/unique_fd.h>
#include <sys/uio.h>
namespace android {
class FdTrigger;
struct RpcTransportFd;
// for 'friend'
class RpcTransportRaw;
class RpcTransportTls;
class RpcTransportTipcAndroid;
class RpcTransportTipcTrusty;
class RpcTransportCtxRaw;
class RpcTransportCtxTls;
class RpcTransportCtxTipcAndroid;
class RpcTransportCtxTipcTrusty;
// Represents a socket connection.
// No thread-safety is guaranteed for these APIs.
class LIBBINDER_EXPORTED RpcTransport {
public:
virtual ~RpcTransport() = default;
/**
* Poll the transport to check whether there is any data ready to read.
*
* Return:
* OK - There is data available on this transport
* WOULDBLOCK - No data is available
* error - any other error
*/
[[nodiscard]] virtual status_t pollRead(void) = 0;
/**
* Read (or write), but allow to be interrupted by a trigger.
*
* iovs - array of iovecs to perform the operation on. The elements
* of the array may be modified by this method.
*
* altPoll - function to be called instead of polling, when needing to wait
* to read/write data. If this returns an error, that error is returned from
* this function.
*
* ancillaryFds - FDs to be sent via UNIX domain dockets or Trusty IPC. When
* reading, if `ancillaryFds` is null, any received FDs will be silently
* dropped and closed (by the OS). Appended values will always be unique_fd,
* the variant type is used to avoid extra copies elsewhere.
*
* Return:
* OK - succeeded in completely processing 'size'
* error - interrupted (failure or trigger)
*/
[[nodiscard]] virtual status_t interruptableWriteFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
const std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>*
ancillaryFds) = 0;
[[nodiscard]] virtual status_t interruptableReadFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>* ancillaryFds) = 0;
/**
* Check whether any threads are blocked while polling the transport
* for read operations
* Return:
* True - Specifies that there is active polling on transport.
* False - No active polling on transport
*/
[[nodiscard]] virtual bool isWaiting() = 0;
private:
// limit the classes which can implement RpcTransport. Being able to change this
// interface is important to allow development of RPC binder. In the past, we
// changed this interface to use iovec for efficiency, and we added FDs to the
// interface. If another transport is needed, it should be added directly here.
// non-socket FDs likely also need changes in RpcSession in order to get
// connected, and similarly to how addrinfo was type-erased from RPC binder
// interfaces when RpcTransportTipc* was added, other changes may be needed
// to add more transports.
friend class ::android::RpcTransportRaw;
friend class ::android::RpcTransportTls;
friend class ::android::RpcTransportTipcAndroid;
friend class ::android::RpcTransportTipcTrusty;
RpcTransport() = default;
};
// Represents the context that generates the socket connection.
// All APIs are thread-safe. See RpcTransportCtxRaw and RpcTransportCtxTls for details.
class LIBBINDER_EXPORTED RpcTransportCtx {
public:
virtual ~RpcTransportCtx() = default;
// Create a new RpcTransport object.
//
// Implementation details: for TLS, this function may incur I/O. |fdTrigger| may be used
// to interrupt I/O. This function blocks until handshake is finished.
[[nodiscard]] virtual std::unique_ptr<RpcTransport> newTransport(
android::RpcTransportFd fd, FdTrigger *fdTrigger) const = 0;
// Return the preconfigured certificate of this context.
//
// Implementation details:
// - For raw sockets, this always returns empty string.
// - For TLS, this returns the certificate. See RpcTransportTls for details.
[[nodiscard]] virtual std::vector<uint8_t> getCertificate(
RpcCertificateFormat format) const = 0;
private:
// see comment on RpcTransport
friend class ::android::RpcTransportCtxRaw;
friend class ::android::RpcTransportCtxTls;
friend class ::android::RpcTransportCtxTipcAndroid;
friend class ::android::RpcTransportCtxTipcTrusty;
RpcTransportCtx() = default;
};
// A factory class that generates RpcTransportCtx.
// All APIs are thread-safe.
class LIBBINDER_EXPORTED RpcTransportCtxFactory {
public:
virtual ~RpcTransportCtxFactory() = default;
// Creates server context.
[[nodiscard]] virtual std::unique_ptr<RpcTransportCtx> newServerCtx() const = 0;
// Creates client context.
[[nodiscard]] virtual std::unique_ptr<RpcTransportCtx> newClientCtx() const = 0;
// Return a short description of this transport (e.g. "raw"). For logging / debugging / testing
// only.
[[nodiscard]] virtual const char *toCString() const = 0;
protected:
RpcTransportCtxFactory() = default;
};
struct LIBBINDER_EXPORTED RpcTransportFd final {
private:
mutable bool isPolling{false};
void setPollingState(bool state) const { isPolling = state; }
public:
binder::unique_fd fd;
RpcTransportFd() = default;
explicit RpcTransportFd(binder::unique_fd&& descriptor)
: isPolling(false), fd(std::move(descriptor)) {}
RpcTransportFd(RpcTransportFd &&transportFd) noexcept
: isPolling(transportFd.isPolling), fd(std::move(transportFd.fd)) {}
RpcTransportFd &operator=(RpcTransportFd &&transportFd) noexcept {
fd = std::move(transportFd.fd);
isPolling = transportFd.isPolling;
return *this;
}
RpcTransportFd& operator=(binder::unique_fd&& descriptor) noexcept {
fd = std::move(descriptor);
isPolling = false;
return *this;
}
bool isInPollingState() const { return isPolling; }
friend class FdTrigger;
};
} // namespace android
@@ -1,42 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Wraps the transport layer of RPC. Implementation uses plain sockets.
// Note: don't use directly. You probably want newServerRpcTransportCtx / newClientRpcTransportCtx.
#pragma once
#include <memory>
#include <binder/Common.h>
#include <binder/RpcTransport.h>
namespace android {
// RpcTransportCtxFactory with TLS disabled.
class RpcTransportCtxFactoryRaw : public RpcTransportCtxFactory {
public:
LIBBINDER_EXPORTED static std::unique_ptr<RpcTransportCtxFactory> make();
LIBBINDER_EXPORTED std::unique_ptr<RpcTransportCtx> newServerCtx() const override;
LIBBINDER_EXPORTED std::unique_ptr<RpcTransportCtx> newClientCtx() const override;
LIBBINDER_EXPORTED const char* toCString() const override;
private:
RpcTransportCtxFactoryRaw() = default;
};
} // namespace android
@@ -1,726 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>
// Set to 1 to enable CallStacks when logging errors
#define SI_DUMP_CALLSTACKS 0
#if SI_DUMP_CALLSTACKS
#include <utils/CallStack.h>
#endif
#include <utils/NativeHandle.h>
#include <functional>
#include <type_traits>
namespace android {
namespace SafeInterface {
/**
* WARNING: Prefer to use AIDL-generated interfaces. Using SafeInterface to generate interfaces
* does not support tracing, and many other AIDL features out of the box. The general direction
* we should go is to migrate safe interface users to AIDL and then remove this so that there
* is only one thing to learn/use/test/integrate, not this as well.
*/
// ParcelHandler is responsible for writing/reading various types to/from a Parcel in a generic way
class LIBBINDER_EXPORTED ParcelHandler {
public:
explicit ParcelHandler(const char* logTag) : mLogTag(logTag) {}
// Specializations for types with dedicated handling in Parcel
status_t read(const Parcel& parcel, bool* b) const {
return callParcel("readBool", [&]() { return parcel.readBool(b); });
}
status_t write(Parcel* parcel, bool b) const {
return callParcel("writeBool", [&]() { return parcel->writeBool(b); });
}
template <typename E>
typename std::enable_if<std::is_enum<E>::value, status_t>::type read(const Parcel& parcel,
E* e) const {
typename std::underlying_type<E>::type u{};
status_t result = read(parcel, &u);
*e = static_cast<E>(u);
return result;
}
template <typename E>
typename std::enable_if<std::is_enum<E>::value, status_t>::type write(Parcel* parcel,
E e) const {
return write(parcel, static_cast<typename std::underlying_type<E>::type>(e));
}
template <typename T>
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type read(
const Parcel& parcel, T* t) const {
return callParcel("read(Flattenable)", [&]() { return parcel.read(*t); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type write(
Parcel* parcel, const T& t) const {
return callParcel("write(Flattenable)", [&]() { return parcel->write(t); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type read(
const Parcel& parcel, sp<T>* t) const {
*t = new T{};
return callParcel("read(sp<Flattenable>)", [&]() { return parcel.read(*(t->get())); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type write(
Parcel* parcel, const sp<T>& t) const {
return callParcel("write(sp<Flattenable>)", [&]() { return parcel->write(*(t.get())); });
}
template <typename T>
typename std::enable_if<std::is_base_of<LightFlattenable<T>, T>::value, status_t>::type read(
const Parcel& parcel, T* t) const {
return callParcel("read(LightFlattenable)", [&]() { return parcel.read(*t); });
}
template <typename T>
typename std::enable_if<std::is_base_of<LightFlattenable<T>, T>::value, status_t>::type write(
Parcel* parcel, const T& t) const {
return callParcel("write(LightFlattenable)", [&]() { return parcel->write(t); });
}
template <typename NH>
typename std::enable_if<std::is_same<NH, sp<NativeHandle>>::value, status_t>::type read(
const Parcel& parcel, NH* nh) {
*nh = NativeHandle::create(parcel.readNativeHandle(), true);
return NO_ERROR;
}
template <typename NH>
typename std::enable_if<std::is_same<NH, sp<NativeHandle>>::value, status_t>::type write(
Parcel* parcel, const NH& nh) {
return callParcel("write(sp<NativeHandle>)",
[&]() { return parcel->writeNativeHandle(nh->handle()); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type read(
const Parcel& parcel, T* t) const {
return callParcel("readParcelable", [&]() { return parcel.readParcelable(t); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type write(
Parcel* parcel, const T& t) const {
return callParcel("writeParcelable", [&]() { return parcel->writeParcelable(t); });
}
status_t read(const Parcel& parcel, String8* str) const {
return callParcel("readString8", [&]() { return parcel.readString8(str); });
}
status_t write(Parcel* parcel, const String8& str) const {
return callParcel("writeString8", [&]() { return parcel->writeString8(str); });
}
template <typename T>
typename std::enable_if<std::is_same<IBinder, T>::value, status_t>::type read(
const Parcel& parcel, sp<T>* pointer) const {
return callParcel("readNullableStrongBinder",
[&]() { return parcel.readNullableStrongBinder(pointer); });
}
template <typename T>
typename std::enable_if<std::is_same<IBinder, T>::value, status_t>::type write(
Parcel* parcel, const sp<T>& pointer) const {
return callParcel("writeStrongBinder",
[&]() { return parcel->writeStrongBinder(pointer); });
}
template <typename T>
typename std::enable_if<std::is_base_of<IInterface, T>::value, status_t>::type read(
const Parcel& parcel, sp<T>* pointer) const {
return callParcel("readNullableStrongBinder[IInterface]",
[&]() { return parcel.readNullableStrongBinder(pointer); });
}
template <typename T>
typename std::enable_if<std::is_base_of<IInterface, T>::value, status_t>::type write(
Parcel* parcel, const sp<T>& interface) const {
return write(parcel, IInterface::asBinder(interface));
}
template <typename T>
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type read(
const Parcel& parcel, std::vector<T>* v) const {
return callParcel("readParcelableVector", [&]() { return parcel.readParcelableVector(v); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type write(
Parcel* parcel, const std::vector<T>& v) const {
return callParcel("writeParcelableVector",
[&]() { return parcel->writeParcelableVector(v); });
}
status_t read(const Parcel& parcel, std::vector<bool>* v) const {
return callParcel("readBoolVector", [&]() { return parcel.readBoolVector(v); });
}
status_t write(Parcel* parcel, const std::vector<bool>& v) const {
return callParcel("writeBoolVector", [&]() { return parcel->writeBoolVector(v); });
}
status_t read(const Parcel& parcel, float* f) const {
return callParcel("readFloat", [&]() { return parcel.readFloat(f); });
}
status_t write(Parcel* parcel, float f) const {
return callParcel("writeFloat", [&]() { return parcel->writeFloat(f); });
}
// Templates to handle integral types. We use a struct template to require that the called
// function exactly matches the signedness and size of the argument (e.g., the argument isn't
// silently widened).
template <bool isSigned, size_t size, typename I>
struct HandleInt;
template <typename I>
struct HandleInt<true, 4, I> {
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
return handler.callParcel("readInt32", [&]() { return parcel.readInt32(i); });
}
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
return handler.callParcel("writeInt32", [&]() { return parcel->writeInt32(i); });
}
};
template <typename I>
struct HandleInt<false, 4, I> {
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
return handler.callParcel("readUint32", [&]() { return parcel.readUint32(i); });
}
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
return handler.callParcel("writeUint32", [&]() { return parcel->writeUint32(i); });
}
};
template <typename I>
struct HandleInt<true, 8, I> {
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
return handler.callParcel("readInt64", [&]() { return parcel.readInt64(i); });
}
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
return handler.callParcel("writeInt64", [&]() { return parcel->writeInt64(i); });
}
};
template <typename I>
struct HandleInt<false, 8, I> {
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
return handler.callParcel("readUint64", [&]() { return parcel.readUint64(i); });
}
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
return handler.callParcel("writeUint64", [&]() { return parcel->writeUint64(i); });
}
};
template <typename I>
typename std::enable_if<std::is_integral<I>::value, status_t>::type read(const Parcel& parcel,
I* i) const {
return HandleInt<std::is_signed<I>::value, sizeof(I), I>::read(*this, parcel, i);
}
template <typename I>
typename std::enable_if<std::is_integral<I>::value, status_t>::type write(Parcel* parcel,
I i) const {
return HandleInt<std::is_signed<I>::value, sizeof(I), I>::write(*this, parcel, i);
}
private:
const char* const mLogTag;
// Helper to encapsulate error handling while calling the various Parcel methods
template <typename Function>
status_t callParcel(const char* name, Function f) const {
status_t error = f();
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to %s, (%d: %s)", name, error, strerror(-error));
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
}
return error;
}
};
// Utility struct template which allows us to retrieve the types of the parameters of a member
// function pointer
template <typename T>
struct ParamExtractor;
template <typename Class, typename Return, typename... Params>
struct ParamExtractor<Return (Class::*)(Params...)> {
using ParamTuple = std::tuple<Params...>;
};
template <typename Class, typename Return, typename... Params>
struct ParamExtractor<Return (Class::*)(Params...) const> {
using ParamTuple = std::tuple<Params...>;
};
} // namespace SafeInterface
template <typename Interface>
class LIBBINDER_EXPORTED SafeBpInterface : public BpInterface<Interface> {
protected:
SafeBpInterface(const sp<IBinder>& impl, const char* logTag)
: BpInterface<Interface>(impl), mLogTag(logTag) {}
~SafeBpInterface() override = default;
// callRemote is used to invoke a synchronous procedure call over Binder
template <typename Method, typename TagType, typename... Args>
status_t callRemote(TagType tag, Args&&... args) const {
static_assert(sizeof(TagType) <= sizeof(uint32_t), "Tag must fit inside uint32_t");
// Verify that the arguments are compatible with the parameters
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
static_assert(ArgsMatchParams<std::tuple<Args...>, ParamTuple>::value,
"Invalid argument type");
// Write the input arguments to the data Parcel
Parcel data;
data.writeInterfaceToken(this->getInterfaceDescriptor());
status_t error = writeInputs(&data, std::forward<Args>(args)...);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by writeInputs
return error;
}
// Send the data Parcel to the remote and retrieve the reply parcel
Parcel reply;
error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply);
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error);
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
return error;
}
// Read the outputs from the reply Parcel into the output arguments
error = readOutputs(reply, std::forward<Args>(args)...);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by readOutputs
return error;
}
// Retrieve the result code from the reply Parcel
status_t result = NO_ERROR;
error = reply.readInt32(&result);
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to obtain result");
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
return error;
}
return result;
}
// callRemoteAsync is used to invoke an asynchronous procedure call over Binder
template <typename Method, typename TagType, typename... Args>
void callRemoteAsync(TagType tag, Args&&... args) const {
static_assert(sizeof(TagType) <= sizeof(uint32_t), "Tag must fit inside uint32_t");
// Verify that the arguments are compatible with the parameters
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
static_assert(ArgsMatchParams<std::tuple<Args...>, ParamTuple>::value,
"Invalid argument type");
// Write the input arguments to the data Parcel
Parcel data;
data.writeInterfaceToken(this->getInterfaceDescriptor());
status_t error = writeInputs(&data, std::forward<Args>(args)...);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by writeInputs
return;
}
// There will be no data in the reply Parcel since the call is one-way
Parcel reply;
error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply,
IBinder::FLAG_ONEWAY);
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error);
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
}
}
private:
const char* const mLogTag;
// This struct provides information on whether the decayed types of the elements at Index in the
// tuple types T and U (that is, the types after stripping cv-qualifiers, removing references,
// and a few other less common operations) are the same
template <size_t Index, typename T, typename U>
struct DecayedElementsMatch {
private:
using FirstT = typename std::tuple_element<Index, T>::type;
using DecayedT = typename std::decay<FirstT>::type;
using FirstU = typename std::tuple_element<Index, U>::type;
using DecayedU = typename std::decay<FirstU>::type;
public:
static constexpr bool value = std::is_same<DecayedT, DecayedU>::value;
};
// When comparing whether the argument types match the parameter types, we first decay them (see
// DecayedElementsMatch) to avoid falsely flagging, say, T&& against T even though they are
// equivalent enough for our purposes
template <typename T, typename U>
struct ArgsMatchParams {};
template <typename... Args, typename... Params>
struct ArgsMatchParams<std::tuple<Args...>, std::tuple<Params...>> {
static_assert(sizeof...(Args) <= sizeof...(Params), "Too many arguments");
static_assert(sizeof...(Args) >= sizeof...(Params), "Not enough arguments");
private:
template <size_t Index>
static constexpr typename std::enable_if<(Index < sizeof...(Args)), bool>::type
elementsMatch() {
if (!DecayedElementsMatch<Index, std::tuple<Args...>, std::tuple<Params...>>::value) {
return false;
}
return elementsMatch<Index + 1>();
}
template <size_t Index>
static constexpr typename std::enable_if<(Index >= sizeof...(Args)), bool>::type
elementsMatch() {
return true;
}
public:
static constexpr bool value = elementsMatch<0>();
};
// Since we assume that pointer arguments are outputs, we can use this template struct to
// determine whether or not a given argument is fundamentally a pointer type and thus an output
template <typename T>
struct IsPointerIfDecayed {
private:
using Decayed = typename std::decay<T>::type;
public:
static constexpr bool value = std::is_pointer<Decayed>::value;
};
template <typename T>
typename std::enable_if<!IsPointerIfDecayed<T>::value, status_t>::type writeIfInput(
Parcel* data, T&& t) const {
return SafeInterface::ParcelHandler{mLogTag}.write(data, std::forward<T>(t));
}
template <typename T>
typename std::enable_if<IsPointerIfDecayed<T>::value, status_t>::type writeIfInput(
Parcel* /*data*/, T&& /*t*/) const {
return NO_ERROR;
}
// This method iterates through all of the arguments, writing them to the data Parcel if they
// are an input (i.e., if they are not a pointer type)
template <typename T, typename... Remaining>
status_t writeInputs(Parcel* data, T&& t, Remaining&&... remaining) const {
status_t error = writeIfInput(data, std::forward<T>(t));
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by writeIfInput
return error;
}
return writeInputs(data, std::forward<Remaining>(remaining)...);
}
static status_t writeInputs(Parcel* /*data*/) { return NO_ERROR; }
template <typename T>
typename std::enable_if<IsPointerIfDecayed<T>::value, status_t>::type readIfOutput(
const Parcel& reply, T&& t) const {
return SafeInterface::ParcelHandler{mLogTag}.read(reply, std::forward<T>(t));
}
template <typename T>
static typename std::enable_if<!IsPointerIfDecayed<T>::value, status_t>::type readIfOutput(
const Parcel& /*reply*/, T&& /*t*/) {
return NO_ERROR;
}
// Similar to writeInputs except that it reads output arguments from the reply Parcel
template <typename T, typename... Remaining>
status_t readOutputs(const Parcel& reply, T&& t, Remaining&&... remaining) const {
status_t error = readIfOutput(reply, std::forward<T>(t));
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by readIfOutput
return error;
}
return readOutputs(reply, std::forward<Remaining>(remaining)...);
}
static status_t readOutputs(const Parcel& /*data*/) { return NO_ERROR; }
};
template <typename Interface>
class LIBBINDER_EXPORTED SafeBnInterface : public BnInterface<Interface> {
public:
explicit SafeBnInterface(const char* logTag) : mLogTag(logTag) {}
protected:
template <typename Method>
status_t callLocal(const Parcel& data, Parcel* reply, Method method) {
CHECK_INTERFACE(this, data, reply);
// Since we need to both pass inputs into the call as well as retrieve outputs, we create a
// "raw" tuple, where the inputs are interleaved with actual, non-pointer versions of the
// outputs. When we ultimately call into the method, we will pass the addresses of the
// output arguments instead of their tuple members directly, but the storage will live in
// the tuple.
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
typename RawConverter<std::tuple<>, ParamTuple>::type rawArgs{};
// Read the inputs from the data Parcel into the argument tuple
status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by read
return error;
}
// Call the local method
status_t result = MethodCaller<ParamTuple>::call(this, method, &rawArgs);
// Extract the outputs from the argument tuple and write them into the reply Parcel
error = OutputWriter<ParamTuple>{mLogTag}.writeOutputs(reply, &rawArgs);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by write
return error;
}
// Return the result code in the reply Parcel
error = reply->writeInt32(result);
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to write result");
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
return error;
}
return NO_ERROR;
}
template <typename Method>
status_t callLocalAsync(const Parcel& data, Parcel* /*reply*/, Method method) {
// reply is not actually used by CHECK_INTERFACE
CHECK_INTERFACE(this, data, reply);
// Since we need to both pass inputs into the call as well as retrieve outputs, we create a
// "raw" tuple, where the inputs are interleaved with actual, non-pointer versions of the
// outputs. When we ultimately call into the method, we will pass the addresses of the
// output arguments instead of their tuple members directly, but the storage will live in
// the tuple.
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
typename RawConverter<std::tuple<>, ParamTuple>::type rawArgs{};
// Read the inputs from the data Parcel into the argument tuple
status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by read
return error;
}
// Call the local method
MethodCaller<ParamTuple>::callVoid(this, method, &rawArgs);
// After calling, there is nothing more to do since asynchronous calls do not return a value
// to the caller
return NO_ERROR;
}
private:
const char* const mLogTag;
// RemoveFirst strips the first element from a tuple.
// For example, given T = std::tuple<A, B, C>, RemoveFirst<T>::type = std::tuple<B, C>
template <typename T, typename... Args>
struct RemoveFirst;
template <typename T, typename... Args>
struct RemoveFirst<std::tuple<T, Args...>> {
using type = std::tuple<Args...>;
};
// RawConverter strips a tuple down to its fundamental types, discarding both pointers and
// references. This allows us to allocate storage for both input (non-pointer) arguments and
// output (pointer) arguments in one tuple.
// For example, given T = std::tuple<const A&, B*>, RawConverter<T>::type = std::tuple<A, B>
template <typename Unconverted, typename... Converted>
struct RawConverter;
template <typename Unconverted, typename... Converted>
struct RawConverter<std::tuple<Converted...>, Unconverted> {
private:
using ElementType = typename std::tuple_element<0, Unconverted>::type;
using Decayed = typename std::decay<ElementType>::type;
using WithoutPointer = typename std::remove_pointer<Decayed>::type;
public:
using type = typename RawConverter<std::tuple<Converted..., WithoutPointer>,
typename RemoveFirst<Unconverted>::type>::type;
};
template <typename... Converted>
struct RawConverter<std::tuple<Converted...>, std::tuple<>> {
using type = std::tuple<Converted...>;
};
// This provides a simple way to determine whether the indexed element of Args... is a pointer
template <size_t I, typename... Args>
struct ElementIsPointer {
private:
using ElementType = typename std::tuple_element<I, std::tuple<Args...>>::type;
public:
static constexpr bool value = std::is_pointer<ElementType>::value;
};
// This class iterates over the parameter types, and if a given parameter is an input
// (i.e., is not a pointer), reads the corresponding argument tuple element from the data Parcel
template <typename... Params>
class InputReader;
template <typename... Params>
class InputReader<std::tuple<Params...>> {
public:
explicit InputReader(const char* logTag) : mLogTag(logTag) {}
// Note that in this case (as opposed to in SafeBpInterface), we iterate using an explicit
// index (starting with 0 here) instead of using recursion and stripping the first element.
// This is because in SafeBpInterface we aren't actually operating on a real tuple, but are
// instead just using a tuple as a convenient container for variadic types, whereas here we
// can't modify the argument tuple without causing unnecessary copies or moves of the data
// contained therein.
template <typename RawTuple>
status_t readInputs(const Parcel& data, RawTuple* args) {
return dispatchArg<0>(data, args);
}
private:
const char* const mLogTag;
template <std::size_t I, typename RawTuple>
typename std::enable_if<!ElementIsPointer<I, Params...>::value, status_t>::type readIfInput(
const Parcel& data, RawTuple* args) {
return SafeInterface::ParcelHandler{mLogTag}.read(data, &std::get<I>(*args));
}
template <std::size_t I, typename RawTuple>
typename std::enable_if<ElementIsPointer<I, Params...>::value, status_t>::type readIfInput(
const Parcel& /*data*/, RawTuple* /*args*/) {
return NO_ERROR;
}
// Recursively iterate through the arguments
template <std::size_t I, typename RawTuple>
typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg(
const Parcel& data, RawTuple* args) {
status_t error = readIfInput<I>(data, args);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged in read
return error;
}
return dispatchArg<I + 1>(data, args);
}
template <std::size_t I, typename RawTuple>
typename std::enable_if<(I >= sizeof...(Params)), status_t>::type dispatchArg(
const Parcel& /*data*/, RawTuple* /*args*/) {
return NO_ERROR;
}
};
// getForCall uses the types of the parameters to determine whether a given element of the
// argument tuple is an input, which should be passed directly into the call, or an output, for
// which its address should be passed into the call
template <size_t I, typename RawTuple, typename... Params>
static typename std::enable_if<
ElementIsPointer<I, Params...>::value,
typename std::tuple_element<I, std::tuple<Params...>>::type>::type
getForCall(RawTuple* args) {
return &std::get<I>(*args);
}
template <size_t I, typename RawTuple, typename... Params>
static typename std::enable_if<
!ElementIsPointer<I, Params...>::value,
typename std::tuple_element<I, std::tuple<Params...>>::type>::type&
getForCall(RawTuple* args) {
return std::get<I>(*args);
}
// This template class uses std::index_sequence and parameter pack expansion to call the given
// method using the elements of the argument tuple (after those arguments are passed through
// getForCall to get addresses instead of values for output arguments)
template <typename... Params>
struct MethodCaller;
template <typename... Params>
struct MethodCaller<std::tuple<Params...>> {
public:
// The calls through these to the helper methods are necessary to generate the
// std::index_sequences used to unpack the argument tuple into the method call
template <typename Class, typename MemberFunction, typename RawTuple>
static status_t call(Class* instance, MemberFunction function, RawTuple* args) {
return callHelper(instance, function, args, std::index_sequence_for<Params...>{});
}
template <typename Class, typename MemberFunction, typename RawTuple>
static void callVoid(Class* instance, MemberFunction function, RawTuple* args) {
callVoidHelper(instance, function, args, std::index_sequence_for<Params...>{});
}
private:
template <typename Class, typename MemberFunction, typename RawTuple, std::size_t... I>
static status_t callHelper(Class* instance, MemberFunction function, RawTuple* args,
std::index_sequence<I...> /*unused*/) {
return (instance->*function)(getForCall<I, RawTuple, Params...>(args)...);
}
template <typename Class, typename MemberFunction, typename RawTuple, std::size_t... I>
static void callVoidHelper(Class* instance, MemberFunction function, RawTuple* args,
std::index_sequence<I...> /*unused*/) {
(instance->*function)(getForCall<I, RawTuple, Params...>(args)...);
}
};
// This class iterates over the parameter types, and if a given parameter is an output
// (i.e., is a pointer), writes the corresponding argument tuple element into the reply Parcel
template <typename... Params>
struct OutputWriter;
template <typename... Params>
struct OutputWriter<std::tuple<Params...>> {
public:
explicit OutputWriter(const char* logTag) : mLogTag(logTag) {}
// See the note on InputReader::readInputs for why this differs from the arguably simpler
// RemoveFirst approach in SafeBpInterface
template <typename RawTuple>
status_t writeOutputs(Parcel* reply, RawTuple* args) {
return dispatchArg<0>(reply, args);
}
private:
const char* const mLogTag;
template <std::size_t I, typename RawTuple>
typename std::enable_if<ElementIsPointer<I, Params...>::value, status_t>::type
writeIfOutput(Parcel* reply, RawTuple* args) {
return SafeInterface::ParcelHandler{mLogTag}.write(reply, std::get<I>(*args));
}
template <std::size_t I, typename RawTuple>
typename std::enable_if<!ElementIsPointer<I, Params...>::value, status_t>::type
writeIfOutput(Parcel* /*reply*/, RawTuple* /*args*/) {
return NO_ERROR;
}
// Recursively iterate through the arguments
template <std::size_t I, typename RawTuple>
typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg(
Parcel* reply, RawTuple* args) {
status_t error = writeIfOutput<I>(reply, args);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged in read
return error;
}
return dispatchArg<I + 1>(reply, args);
}
template <std::size_t I, typename RawTuple>
typename std::enable_if<(I >= sizeof...(Params)), status_t>::type dispatchArg(
Parcel* /*reply*/, RawTuple* /*args*/) {
return NO_ERROR;
}
};
};
} // namespace android
@@ -1,176 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#include <string>
class BinderStabilityIntegrationTest_ExpectedStabilityForItsPartition_Test;
namespace android {
class BpBinder;
class ProcessState;
namespace internal {
// Stability encodes how a binder changes over time. There are two levels of
// stability:
// 1). the interface stability - this is how a particular set of API calls (a
// particular ordering of things like writeInt32/readInt32) are changed over
// time. If one release, we have 'writeInt32' and the next release, we have
// 'writeInt64', then this interface doesn't have a very stable
// Stability::Level. Usually this ordering is controlled by a .aidl file.
// 2). the wire format stability - this is how these API calls map to actual
// bytes that are written to the wire (literally, this is how they are written
// to the kernel inside of IBinder::transact, but it may be expanded to other
// wires in the future). For instance, writeInt32 in binder translates to
// writing a 4-byte little-endian integer in two's complement. You can imagine
// in the future, we change writeInt32/readInt32 to instead write 8-bytes with
// that integer and some check bits. In this case, the wire format changes,
// but as long as a client libbinder knows to keep on writing a 4-byte value
// to old servers, and new servers know how to interpret the 8-byte result,
// they can still communicate.
//
// This class is specifically about (1). (2) is not currently tracked by
// libbinder for regular binder calls, and everything on the system uses the
// same copy of libbinder.
class Stability final {
public:
// Given a binder interface at a certain stability, there may be some
// requirements associated with that higher stability level. For instance, a
// VINTF stability binder is required to be in the VINTF manifest. This API
// can be called to use that same interface within the local partition.
LIBBINDER_EXPORTED static void forceDowngradeToLocalStability(const sp<IBinder>& binder);
// WARNING: Below APIs are only ever expected to be called by auto-generated code.
// Instead of calling them, you should set the stability of a .aidl interface
// WARNING: The only client of
// - forceDowngradeToSystemStability() and;
// - korceDowngradeToVendorStability()
// should be AIBinder_forceDowngradeToLocalStability().
//
// getLocalLevel() in libbinder returns Level::SYSTEM when called
// from libbinder_ndk (even on vendor partition). So we explicitly provide
// these methods for use by the NDK API:
// AIBinder_forceDowngradeToLocalStability().
//
// This allows correctly downgrading the binder's stability to either system/vendor,
// depending on the partition.
// Given a binder interface at a certain stability, there may be some
// requirements associated with that higher stability level. For instance, a
// VINTF stability binder is required to be in the VINTF manifest. This API
// can be called to use that same interface within the vendor partition.
LIBBINDER_EXPORTED static void forceDowngradeToVendorStability(const sp<IBinder>& binder);
// Given a binder interface at a certain stability, there may be some
// requirements associated with that higher stability level. For instance, a
// VINTF stability binder is required to be in the VINTF manifest. This API
// can be called to use that same interface within the system partition.
LIBBINDER_EXPORTED static void forceDowngradeToSystemStability(const sp<IBinder>& binder);
// WARNING: This is only ever expected to be called by auto-generated code. You likely want to
// change or modify the stability class of the interface you are using.
// This must be called as soon as the binder in question is constructed. No thread safety
// is provided.
// E.g. stability is according to libbinder compilation unit
LIBBINDER_EXPORTED static void markCompilationUnit(IBinder* binder);
// WARNING: This is only ever expected to be called by auto-generated code. You likely want to
// change or modify the stability class of the interface you are using.
// This must be called as soon as the binder in question is constructed. No thread safety
// is provided.
// E.g. stability is according to libbinder_ndk or Java SDK AND the interface
// expressed here is guaranteed to be stable for multiple years (Stable AIDL)
LIBBINDER_EXPORTED static void markVintf(IBinder* binder);
// WARNING: for debugging only
LIBBINDER_EXPORTED static std::string debugToString(const sp<IBinder>& binder);
// WARNING: This is only ever expected to be called by auto-generated code or tests.
// You likely want to change or modify the stability of the interface you are using.
// This must be called as soon as the binder in question is constructed. No thread safety
// is provided.
// E.g. stability is according to libbinder_ndk or Java SDK AND the interface
// expressed here is guaranteed to be stable for multiple years (Stable AIDL)
// If this is called when __ANDROID_VNDK__ is not defined, then it is UB and will likely
// break the device during GSI or other tests.
LIBBINDER_EXPORTED static void markVndk(IBinder* binder);
// Returns true if the binder needs to be declared in the VINTF manifest or
// else false if the binder is local to the current partition.
LIBBINDER_EXPORTED static bool requiresVintfDeclaration(const sp<IBinder>& binder);
private:
// Parcel needs to read/write stability level in an unstable format.
friend ::android::Parcel;
// only expose internal APIs inside of libbinder, for checking stability
friend ::android::BpBinder;
// so that it can mark the context object (only the root object doesn't go
// through Parcel)
friend ::android::ProcessState;
friend ::BinderStabilityIntegrationTest_ExpectedStabilityForItsPartition_Test;
static void tryMarkCompilationUnit(IBinder* binder);
// Currently, we use int16_t for Level so that it can fit in BBinder.
// However, on the wire, we have 4 bytes reserved for stability, so whenever
// we ingest a Level, we always accept an int32_t.
enum Level : int16_t {
UNDECLARED = 0,
VENDOR = 0b000011,
SYSTEM = 0b001100,
VINTF = 0b111111,
};
// returns the stability according to how this was built
static Level getLocalLevel();
// Downgrades binder stability to the specified level.
static void forceDowngradeToStability(const sp<IBinder>& binder, Level level);
enum {
REPR_NONE = 0,
REPR_LOG = 1,
REPR_ALLOW_DOWNGRADE = 2,
};
// applies stability to binder if stability level is known
__attribute__((warn_unused_result)) static status_t setRepr(IBinder* binder, int32_t setting,
uint32_t flags);
// get stability information as encoded on the wire
LIBBINDER_EXPORTED static int16_t getRepr(IBinder* binder);
// whether a transaction on binder is allowed, if the transaction
// is done from a context with a specific stability level
LIBBINDER_EXPORTED static bool check(int16_t provided, Level required);
static bool isDeclaredLevel(int32_t level);
static std::string levelString(int32_t level);
Stability();
};
} // namespace internal
} // namespace android
@@ -1,205 +0,0 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <utils/Errors.h>
#include <utils/String8.h>
#include <stdint.h>
#include <string.h>
#include <sstream>
// ---------------------------------------------------------------------------
namespace android {
class LIBBINDER_EXPORTED TextOutput {
public:
TextOutput();
virtual ~TextOutput();
virtual status_t print(const char* txt, size_t len) = 0;
virtual void moveIndent(int delta) = 0;
class Bundle {
public:
inline explicit Bundle(TextOutput& to) : mTO(to) { to.pushBundle(); }
inline ~Bundle() { mTO.popBundle(); }
private:
TextOutput& mTO;
};
virtual void pushBundle() = 0;
virtual void popBundle() = 0;
};
// ---------------------------------------------------------------------------
// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
// be allocated.
// Text output stream for printing to the log (via utils/Log.h).
extern LIBBINDER_EXPORTED TextOutput& alog;
// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
// be allocated.
// Text output stream for printing to stdout.
extern LIBBINDER_EXPORTED TextOutput& aout;
// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
// be allocated.
// Text output stream for printing to stderr.
extern LIBBINDER_EXPORTED TextOutput& aerr;
typedef TextOutput& (*TextOutputManipFunc)(TextOutput&);
TextOutput& endl(TextOutput& to);
TextOutput& indent(TextOutput& to);
TextOutput& dedent(TextOutput& to);
template<typename T>
TextOutput& operator<<(TextOutput& to, const T& val)
{
std::stringstream strbuf;
strbuf << val;
std::string str = strbuf.str();
to.print(str.c_str(), str.size());
return to;
}
LIBBINDER_EXPORTED TextOutput& operator<<(TextOutput& to, TextOutputManipFunc func);
class LIBBINDER_EXPORTED TypeCode {
public:
inline explicit TypeCode(uint32_t code);
inline ~TypeCode();
inline uint32_t typeCode() const;
private:
uint32_t mCode;
};
LIBBINDER_EXPORTED std::ostream& operator<<(std::ostream& to, const TypeCode& val);
class LIBBINDER_EXPORTED HexDump {
public:
HexDump(const void *buf, size_t size, size_t bytesPerLine=16);
inline ~HexDump();
inline HexDump& setBytesPerLine(size_t bytesPerLine);
inline HexDump& setSingleLineCutoff(int32_t bytes);
inline HexDump& setAlignment(size_t alignment);
inline HexDump& setCArrayStyle(bool enabled);
inline const void* buffer() const;
inline size_t size() const;
inline size_t bytesPerLine() const;
inline int32_t singleLineCutoff() const;
inline size_t alignment() const;
inline bool carrayStyle() const;
private:
const void* mBuffer;
size_t mSize;
size_t mBytesPerLine;
int32_t mSingleLineCutoff;
size_t mAlignment;
bool mCArrayStyle;
};
LIBBINDER_EXPORTED std::ostream& operator<<(std::ostream& to, const HexDump& val);
inline TextOutput& operator<<(TextOutput& to,
decltype(std::endl<char,
std::char_traits<char>>)
/*val*/) {
endl(to);
return to;
}
inline TextOutput& operator<<(TextOutput& to, const char &c)
{
to.print(&c, 1);
return to;
}
inline TextOutput& operator<<(TextOutput& to, const bool &val)
{
if (val) to.print("true", 4);
else to.print("false", 5);
return to;
}
inline TextOutput& operator<<(TextOutput& to, const String16& val)
{
to << String8(val).c_str();
return to;
}
// ---------------------------------------------------------------------------
// No user servicable parts below.
inline TextOutput& endl(TextOutput& to)
{
to.print("\n", 1);
return to;
}
inline TextOutput& indent(TextOutput& to)
{
to.moveIndent(1);
return to;
}
inline TextOutput& dedent(TextOutput& to)
{
to.moveIndent(-1);
return to;
}
inline TextOutput& operator<<(TextOutput& to, TextOutputManipFunc func)
{
return (*func)(to);
}
inline TypeCode::TypeCode(uint32_t code) : mCode(code) { }
inline TypeCode::~TypeCode() { }
inline uint32_t TypeCode::typeCode() const { return mCode; }
inline HexDump::~HexDump() { }
inline HexDump& HexDump::setBytesPerLine(size_t bytesPerLine) {
mBytesPerLine = bytesPerLine; return *this;
}
inline HexDump& HexDump::setSingleLineCutoff(int32_t bytes) {
mSingleLineCutoff = bytes; return *this;
}
inline HexDump& HexDump::setAlignment(size_t alignment) {
mAlignment = alignment; return *this;
}
inline HexDump& HexDump::setCArrayStyle(bool enabled) {
mCArrayStyle = enabled; return *this;
}
inline const void* HexDump::buffer() const { return mBuffer; }
inline size_t HexDump::size() const { return mSize; }
inline size_t HexDump::bytesPerLine() const { return mBytesPerLine; }
inline int32_t HexDump::singleLineCutoff() const { return mSingleLineCutoff; }
inline size_t HexDump::alignment() const { return mAlignment; }
inline bool HexDump::carrayStyle() const { return mCArrayStyle; }
// ---------------------------------------------------------------------------
} // namespace android
-59
View File
@@ -1,59 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#if __has_include(<cutils/trace.h>)
#include <cutils/trace.h>
#endif
#include <binder/Common.h>
#ifdef ATRACE_TAG_AIDL
#if ATRACE_TAG_AIDL != (1 << 24)
#error "Mismatched ATRACE_TAG_AIDL definitions"
#endif
#else
#define ATRACE_TAG_AIDL (1 << 24)
#endif
namespace android {
namespace binder {
// Forward declarations from internal OS.h
namespace os {
// Trampoline functions allowing generated aidls to trace binder transactions without depending on
// libcutils/libutils
void trace_begin(uint64_t tag, const char* name);
void trace_end(uint64_t tag);
void trace_int(uint64_t tag, const char* name, int32_t value);
uint64_t get_trace_enabled_tags();
} // namespace os
class LIBBINDER_EXPORTED ScopedTrace {
public:
inline ScopedTrace(uint64_t tag, const char* name) : mTag(tag) { os::trace_begin(mTag, name); }
inline ~ScopedTrace() { os::trace_end(mTag); }
private:
uint64_t mTag;
};
} // namespace binder
} // namespace android
+4 -2
View File
@@ -221,7 +221,7 @@
// LightRefBase used to be declared in this header, so we have to include it // LightRefBase used to be declared in this header, so we have to include it
#include <utils/LightRefBase.h> #include <utils/LightRefBase.h>
#include <utils/StrongPointer.h> #include "utils/StrongPointer.h"
#include <utils/TypeHelpers.h> #include <utils/TypeHelpers.h>
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -542,7 +542,9 @@ wp<T> wp<T>::fromExisting(T* other) {
if (!other) return nullptr; if (!other) return nullptr;
auto refs = other->getWeakRefs(); auto refs = other->getWeakRefs();
refs->incWeakRequireWeak(other); //TrickyStoreOSS edit
//refs->incWeakRequireWeak(other);
refs->incWeak(other);
wp<T> ret; wp<T> ret;
ret.m_ptr = other; ret.m_ptr = other;
@@ -30,7 +30,7 @@ template<typename T> class wp;
template<typename T> template<typename T>
class sp { class sp {
public: public:
inline constexpr sp() : m_ptr(nullptr) { } inline sp() : m_ptr(nullptr) { }
// The old way of using sp<> was like this. This is bad because it relies // The old way of using sp<> was like this. This is bad because it relies
// on implicit conversion to sp<>, which we would like to remove (if an // on implicit conversion to sp<>, which we would like to remove (if an
-4
View File
@@ -20,7 +20,6 @@
#include <stdint.h> #include <stdint.h>
#include <sys/types.h> #include <sys/types.h>
// #include <log/log.h>
#include <utils/TypeHelpers.h> #include <utils/TypeHelpers.h>
#include <utils/VectorImpl.h> #include <utils/VectorImpl.h>
#ifndef __has_attribute #ifndef __has_attribute
@@ -273,9 +272,6 @@ TYPE* Vector<TYPE>::editArray() {
template<class TYPE> inline template<class TYPE> inline
const TYPE& Vector<TYPE>::operator[](size_t index) const { const TYPE& Vector<TYPE>::operator[](size_t index) const {
LOG_FATAL_IF(index>=size(),
"%s: index=%u out of range (%u)", __PRETTY_FUNCTION__,
int(index), int(size()));
return *(array() + index); return *(array() + index);
} }
@@ -38,58 +38,10 @@ enum {
BINDER_TYPE_PTR = B_PACK_CHARS('p', 't', '*', B_TYPE_LARGE), BINDER_TYPE_PTR = B_PACK_CHARS('p', 't', '*', B_TYPE_LARGE),
}; };
/** enum {
* enum flat_binder_object_shifts: shift values for flat_binder_object_flags
* @FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT: shift for getting scheduler policy.
*
*/
enum flat_binder_object_shifts {
FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT = 9,
};
/**
* enum flat_binder_object_flags - flags for use in flat_binder_object.flags
*/
enum flat_binder_object_flags {
/**
* @FLAT_BINDER_FLAG_PRIORITY_MASK: bit-mask for min scheduler priority
*
* These bits can be used to set the minimum scheduler priority
* at which transactions into this node should run. Valid values
* in these bits depend on the scheduler policy encoded in
* @FLAT_BINDER_FLAG_SCHED_POLICY_MASK.
*
* For SCHED_NORMAL/SCHED_BATCH, the valid range is between [-20..19]
* For SCHED_FIFO/SCHED_RR, the value can run between [1..99]
*/
FLAT_BINDER_FLAG_PRIORITY_MASK = 0xff, FLAT_BINDER_FLAG_PRIORITY_MASK = 0xff,
/**
* @FLAT_BINDER_FLAG_ACCEPTS_FDS: whether the node accepts fds.
*/
FLAT_BINDER_FLAG_ACCEPTS_FDS = 0x100, FLAT_BINDER_FLAG_ACCEPTS_FDS = 0x100,
/**
* @FLAT_BINDER_FLAG_SCHED_POLICY_MASK: bit-mask for scheduling policy
*
* These two bits can be used to set the min scheduling policy at which
* transactions on this node should run. These match the UAPI
* scheduler policy values, eg:
* 00b: SCHED_NORMAL
* 01b: SCHED_FIFO
* 10b: SCHED_RR
* 11b: SCHED_BATCH
*/
FLAT_BINDER_FLAG_SCHED_POLICY_MASK =
3U << FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT,
/**
* @FLAT_BINDER_FLAG_INHERIT_RT: whether the node inherits RT policy
*
* Only when set, calls into this node will inherit a real-time
* scheduling policy from the caller (for synchronous transactions).
*/
FLAT_BINDER_FLAG_INHERIT_RT = 0x800,
/** /**
* @FLAT_BINDER_FLAG_TXN_SECURITY_CTX: request security contexts * @FLAT_BINDER_FLAG_TXN_SECURITY_CTX: request security contexts
* *
@@ -637,3 +589,4 @@ enum binder_driver_command_protocol {
}; };
#endif /* _UAPI_LINUX_BINDER_H */ #endif /* _UAPI_LINUX_BINDER_H */
-10
View File
@@ -1,10 +0,0 @@
#pragma once
#include <android/log.h>
#include <errno.h>
#ifndef LOG_TAG
#define LOG_TAG "TEESimulator"
#endif
#include "../logging.hpp"
-499
View File
@@ -1,499 +0,0 @@
#pragma once
#include <algorithm> // For std::swap in UniqueFd
#include <limits.h> // For PATH_MAX
#include <string>
#include <string_view>
#include <sys/ptrace.h>
#include <unistd.h>
#include <vector>
#include "lsplt.hpp"
// Macros for syscall error checking. These are typically used after remote
// syscall emulation.
#define SYSCALL_IS_ERR(e) (((unsigned long)e) > -4096UL) // Checks if a syscall return value indicates an error.
#define SYSCALL_ERR(e) (-(int)(e)) // Converts a syscall error value to a negative errno.
// Architecture-specific register definitions.
// These macros abstract away the differences in register names across architectures,
// allowing for generic code that manipulates `struct user_regs_struct`.
#if defined(__x86_64__)
# define REG_SP rsp // Stack pointer register
# define REG_IP rip // Instruction pointer register
# define REG_RET rax // Return value register
# define REG_NR orig_rax // Syscall number register
# define REG_SYS_ARG0 rdi // First syscall argument register
#elif defined(__i386__)
# define REG_SP esp
# define REG_IP eip
# define REG_RET eax
# define REG_NR orig_eax
# define REG_SYS_ARG0 ebx
#elif defined(__aarch64__)
# define REG_SP sp // Stack pointer register (AArch64)
# define REG_IP pc // Program counter register (AArch64)
# define REG_RET regs[0] // Return value register (x0)
# define REG_NR regs[8] // Syscall number register (x8)
# define REG_SYS_ARG0 regs[0] // First syscall argument register (x0)
#elif defined(__arm__)
# define REG_SP uregs[13] // Stack pointer register (R13)
# define REG_IP uregs[15] // Program counter register (R15)
# define REG_RET uregs[0] // Return value register (R0)
# define REG_NR uregs[7] // Syscall number register (R7)
# define REG_SYS_ARG0 uregs[0] // First syscall argument register (R0)
# define user_regs_struct user_regs // ARM's equivalent to user_regs_struct is user_regs
# define SYS_mmap SYS_mmap2 // ARM uses mmap2 syscall
#endif
// --- Remote Memory Operations ---
/**
* @brief Writes data to the remote process's memory.
* @param pid The target process ID.
* @param remote_addr The target address in the remote process.
* @param buf A pointer to the local buffer containing data to write.
* @param len The number of bytes to write.
* @param use_proc_mem If true, uses /proc/<pid>/mem; otherwise, uses
* process_vm_writev.
* @return The number of bytes written, or -1 on error.
*/
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len, bool use_proc_mem = false);
/**
* @brief Reads data from the remote process's memory.
* @param pid The target process ID.
* @param remote_addr The source address in the remote process.
* @param buf A pointer to the local buffer to store the read data.
* @param len The number of bytes to read.
* @return The number of bytes read, or -1 on error.
*/
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len);
// --- Remote Register Operations ---
/**
* @brief Retrieves the current CPU registers of the target process.
* @param pid The target process ID.
* @param regs A reference to a `user_regs_struct` to store the registers.
* @return True on success, false on failure.
*/
bool get_regs(int pid, struct user_regs_struct &regs);
/**
* @brief Sets the CPU registers of the target process.
* @param pid The target process ID.
* @param regs A reference to a `user_regs_struct` containing the registers to set.
* @return True on success, false on failure.
*/
bool set_regs(int pid, struct user_regs_struct &regs);
// --- Module and Symbol Resolution ---
/**
* @brief Gets a descriptive string of the memory region containing a given
* address.
* @param map_info A vector of `lsplt::MapInfo` for the process.
* @param addr The address to look up.
* @return A string representing the memory region (e.g., "path perms"), or "<unknown>".
*/
std::string get_addr_mem_region(const std::vector<lsplt::MapInfo> &map_info, uintptr_t addr);
/**
* @brief Finds the base address of a module in a process's memory map.
* @param map_info A vector of `lsplt::MapInfo` for the process.
* @param module_suffix The suffix of the module path (e.g., "libc.so").
* @return The base address of the module, or nullptr if not found.
*/
void *find_module_base(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
/**
* @brief Finds the address of a function in a remote process by resolving it
* locally and calculating the offset.
*
* This function opens the module locally, finds the symbol address,
* calculates its offset from the local module base, and then adds that offset to the remote module base.
*
* @param local_map_info Memory map of the local (injector) process.
* @param remote_map_info Memory map of the remote (target) process.
* @param module_name The name of the module (e.g., "libc.so").
* @param function_name The name of the function (e.g., "open").
* @return The remote address of the function, or nullptr if not found.
*/
void *find_func_addr(const std::vector<lsplt::MapInfo> &local_map_info,
const std::vector<lsplt::MapInfo> &remote_map_info, std::string_view module_name,
std::string_view function_name);
/**
* @brief Finds a suitable return address within a specific module in the remote
* process.
*
* This typically looks for a non-executable segment of the module to return to,
* as `PTRACE_CONT` will resume execution at the specified instruction pointer.
*
* @param map_info A vector of `lsplt::MapInfo` for the remote process.
* @param module_suffix The suffix of the module path (e.g., "libc.so").
* @return A pointer to a suitable return address, or nullptr if not found.
*/
void *find_module_return_addr(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
// --- Remote Stack Manipulation ---
/**
* @brief Aligns the stack pointer (`REG_SP`) to ensure proper stack frame setup.
* @param regs A reference to the `user_regs_struct` to modify.
* @param preserve_bytes Number of bytes to preserve below the new stack pointer.
*/
void align_stack(struct user_regs_struct &regs, uintptr_t preserve_bytes = 0);
/**
* @brief Pushes a block of memory onto the remote process's stack.
*
* This function decrements the stack pointer, aligns it, and then writes the data.
*
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (its stack pointer will be updated).
* @param data A pointer to the local data to push.
* @param length The number of bytes to push.
* @return The remote address where the data was pushed, or 0 on error.
*/
uintptr_t push_memory(int pid, struct user_regs_struct &regs, const void *data, size_t length);
/**
* @brief Pushes a null-terminated string onto the remote process's stack.
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (its stack pointer will be updated).
* @param str The null-terminated C-style string to push.
* @return The remote address where the string was pushed, or 0 on error.
*/
uintptr_t push_string(int pid, struct user_regs_struct &regs, const char *str);
// --- Remote Function Call Emulation ---
/**
* @brief Prepares and initiates a remote function call in the target process.
*
* This function sets up registers (arguments, return address, instruction pointer) and
* then continues the target process execution using PTRACE_CONT.
*
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (will be modified).
* @param func_addr The remote address of the function to call.
* @param return_addr The address in the remote process where execution should
* resume after the call.
* @param args A vector of `uintptr_t` representing the function arguments.
* @return True if the remote call was successfully initiated, false otherwise.
*/
bool remote_pre_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &args);
/**
* @brief Waits for and finalizes a remote function call, retrieving its return value.
*
* This function waits for the target process to stop after a remote call and
* then retrieves the return value from the appropriate register.
*
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (will be updated with post-call registers).
* @param expected_return_addr The address where the remote call was expected to return to.
* Used for error checking (e.g., if a crash occurs elsewhere).
* @return The return value of the remote function, or 0 on error.
*/
uintptr_t remote_post_call(int pid, struct user_regs_struct &regs, uintptr_t expected_return_addr);
/**
* @brief Executes a complete remote function call (pre-call, continue,
* post-call).
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (will be modified).
* @param func_addr The remote address of the function to call.
* @param return_addr The address in the remote process where execution should resume after the call.
* @param args A vector of `uintptr_t` representing the function arguments.
* @return The return value of the remote function, or 0 on error.
*/
uintptr_t remote_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &args);
// --- Process Management and Ptrace Utilities ---
/**
* @brief Forks twice to create a daemon process, returning 0 in the daemon,
* or the child pid in parent.
* @return 0 in the grand-child (daemon), PID of first child in parent, or -1 on error.
*/
int fork_dont_care();
/**
* @brief Waits for the target process to stop due to ptrace.
*
* This function handles `EINTR` and ensures the process is actually stopped.
*
* @param pid The target process ID.
* @param status A pointer to an integer to store the wait status.
* @param flags Flags for `waitpid` (e.g., `__WALL`).
* @return True if the process successfully stopped, false otherwise.
*/
bool wait_for_trace(int pid, int *status, int flags);
/**
* @brief Parses the wait status integer into a human-readable string.
* @param status The status integer returned by `waitpid`.
* @return A string describing the wait status.
*/
std::string parse_status(int status);
/**
* @brief Retrieves the executable path of a process.
* @param pid The target process ID.
* @return The absolute path to the executable, or an empty string on error.
*/
std::string get_program(int pid);
/**
* @brief Gets the command-line arguments of a process.
* @param pid The target process ID.
* @return A vector of strings representing the command-line arguments.
*/
std::vector<std::string> get_cmdline(int pid);
/**
* @brief Parses the `exec` status of a process
* @param pid The target process ID.
* @return A string representing the `exec` status (placeholder).
*/
std::string parse_exec(int pid);
/**
* @brief Skips the current syscall in the target process
* @param pid The target process ID.
* @return True on success, false on failure (placeholder).
*/
bool skip_syscall(int pid);
/**
* @brief Executes a syscall in the remote process using ptrace.
* @param pid The target process ID.
* @param ret Reference to store the syscall return value.
* @param nr The syscall number.
* @param arg0 to arg5 - Syscall arguments.
* @return True on success, false on failure.
*/
bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0,
uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0);
/**
* @brief Switches the mount namespace of the current process to that of the target PID, or restores it.
* @param pid If non-zero, switches to the namespace of `pid`.
* If zero, restores to the namespace stored in `*fd`.
* @param fd On entry (pid != 0), points to an int to store the original namespace FD.
* On entry (pid == 0), points to the FD of the namespace to restore to.
* FD is consumed/set to kInvalidFd on successful restore.
* @return True on success, false on failure.
*/
bool switch_mnt_ns(int pid, int *fd);
/**
* @brief Remotely calls mmap in the target process.
* @param pid The target process ID.
* @param addr The preferred starting address for the new mapping.
* @param size The length of the mapping.
* @param prot Protection flags (PROT_READ, PROT_WRITE, PROT_EXEC).
* @param flags Mapping flags (MAP_PRIVATE, MAP_ANONYMOUS, etc.).
* @param fd File descriptor to map from (or -1 for anonymous).
* @param offset Offset into the file (or 0 for anonymous).
* @return The starting address of the new mapping, or MAP_FAILED on error.
*/
uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset);
/**
* @brief Remotely calls munmap in the target process.
* @param pid The target process ID.
* @param addr The starting address of the region to unmap.
* @param size The length of the region to unmap.
* @return True on success, false on failure.
*/
bool remote_munmap(int pid, uintptr_t addr, size_t size);
/**
* @brief Remotely calls open in the target process.
* @param pid The target process ID.
* @param path_addr The remote address of the path string.
* @param flags Open flags (O_RDONLY, O_WRONLY, O_CREAT, etc.).
* @return The file descriptor in the remote process, or -1 on error.
*/
int remote_open(int pid, uintptr_t path_addr, int flags);
/**
* @brief Remotely calls close in the target process.
* @param pid The target process ID.
* @param fd The file descriptor in the remote process to close.
* @return True on success, false on failure.
*/
bool remote_close(int pid, int fd);
/**
* @brief Waits for a child process to terminate.
* @param pid The child process ID.
* @return The exit status of the child, or -1 on error.
*/
int wait_for_child(int pid);
/**
* @brief Determines the ELF class (32-bit or 64-bit) of an executable file.
* @param path The path to the ELF file.
* @return `ELFCLASS32` for 32-bit, `ELFCLASS64` for 64-bit, or `ELFNONE` on error.
*/
int get_elf_class(std::string_view path);
// --- Miscellaneous Utilities ---
constexpr size_t kMaxPathLength = PATH_MAX; // Max path length, consistent with main.cpp
constexpr size_t kDefaultMagicLength = 16; // Default length for generated magic strings.
/**
* @brief Generates a random alphanumeric string.
* @param length The desired length of the magic string.
* @return The generated magic string.
*/
std::string generateMagic(size_t length);
/**
* @brief Sets the SELinux security context of a file.
* @param file_path The path to the file.
* @param security_context The new security context string.
* @return 0 on success, -1 on failure.
*/
int setfilecon(const char *file_path, const char *security_context);
/**
* @brief RAII wrapper for file descriptors.
*
* This class automatically closes the file descriptor when it goes out of scope.
*/
class UniqueFd {
using Fd = int; // Alias for file descriptor type.
public:
/**
* @brief Default constructor. Initializes with an invalid FD.
*/
UniqueFd() = default;
/**
* @brief Constructor that takes an existing file descriptor.
* @param fd The file descriptor to manage.
*/
UniqueFd(Fd fd) : fd_(fd) {}
/**
* @brief Destructor. Closes the managed file descriptor if valid.
*/
~UniqueFd() {
if (fd_ >= 0)
close(fd_);
}
// Delete copy constructor and assignment operator to prevent double-free issues.
UniqueFd(const UniqueFd &) = delete;
UniqueFd &operator=(const UniqueFd &) = delete;
/**
* @brief Move constructor. Transfers ownership of the file descriptor.
* @param other The `UniqueFd` object to move from.
*/
UniqueFd(UniqueFd &&other) noexcept {
std::swap(fd_, other.fd_);
}
/**
* @brief Move assignment operator. Transfers ownership of the file descriptor.
* @param other The `UniqueFd` object to move from.
* @return A reference to this `UniqueFd` object.
*/
UniqueFd &operator=(UniqueFd &&other) noexcept {
if (this != &other) { // Handle self-assignment
if (fd_ >= 0)
close(fd_); // Close current FD before taking ownership
fd_ = -1; // Invalidate current FD before swap
std::swap(fd_, other.fd_);
}
return *this;
}
/**
* @brief Assignment from raw int FD. Closes the current FD.
*/
UniqueFd &operator=(Fd fd) {
if (fd_ >= 0) {
close(fd_);
}
fd_ = fd;
return *this;
}
/**
* @brief Allows implicit conversion to the underlying file descriptor type.
* @return The managed file descriptor.
*/
operator const Fd &() const {
return fd_;
}
private:
Fd fd_ = -1; // The managed file descriptor, initialized to invalid.
};
/**
* @brief Sets the SELinux context for newly created sockets.
*
* This allows the injector to create sockets with a specific security context
* that might be required for interaction with target processes under SELinux.
* It attempts to write to `/proc/thread-self/attr/sockcreate` or a process-specific fallback.
*
* @param security_context The SELinux context string to set.
* @return True on success, false on failure.
*/
bool set_sockcreate_con(const char *security_context);
// --- Ptrace Event and Signal Parsing ---
#define WPTEVENT(x) (x >> 16) // Macro to extract the ptrace event code from wait status.
#define CASE_CONST_RETURN(x) \
case x: \
return #x; // Helper macro for switch-case to return string literal.
/**
* @brief Parses a ptrace event code into a human-readable string.
* @param status The wait status containing the ptrace event code.
* @return A string representing the ptrace event.
*/
inline const char *parse_ptrace_event(int status) {
status = WPTEVENT(status); // Extract the event code.
switch (status) {
CASE_CONST_RETURN(PTRACE_EVENT_FORK)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK)
CASE_CONST_RETURN(PTRACE_EVENT_CLONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXEC)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXIT)
CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP)
CASE_CONST_RETURN(PTRACE_EVENT_STOP) // Not a standard event, but sometimes
// seen for special stops
default:
return "(no event)"; // Default for unknown or no event.
}
}
/**
* @brief Returns the abbreviated name of a signal.
* @param sig The signal number.
* @return The abbreviated signal name (e.g., "SIGSEGV"), or "(unknown)".
*/
inline const char *sigabbrev_np(int sig) {
// NSIG is the total number of signals, sys_signame array is indexed by signal
// number. Note: sys_signame is part of glibc and may require _GNU_SOURCE or
// similar. Assuming its availability for professional refactor.
if (sig > 0 && sig < NSIG)
return sys_signame[sig];
return "(unknown)";
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <sys/ptrace.h>
#include <unistd.h>
#include <map>
#include <string>
#include "lsplt.hpp"
#define LOG_TAG "TEESimulator"
#define SYSCALL_IS_ERR(e) (((unsigned long)e) > -4096UL)
#define SYSCALL_ERR(e) (-(int)(e))
#if defined(__x86_64__)
# define REG_SP rsp
# define REG_IP rip
# define REG_RET rax
# define REG_NR orig_rax
# define REG_SYS_ARG0 rdi
#elif defined(__i386__)
# define REG_SP esp
# define REG_IP eip
# define REG_RET eax
# define REG_NR orig_eax
# define REG_SYS_ARG0 ebx
#elif defined(__aarch64__)
# define REG_SP sp
# define REG_IP pc
# define REG_RET regs[0]
# define REG_NR regs[8]
# define REG_SYS_ARG0 regs[0]
#elif defined(__arm__)
# define REG_SP uregs[13]
# define REG_IP uregs[15]
# define REG_RET uregs[0]
# define REG_NR uregs[7]
# define REG_SYS_ARG0 uregs[0]
# define user_regs_struct user_regs
# define SYS_mmap SYS_mmap2
#endif
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len, bool use_proc_mem = false);
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len);
bool get_regs(int pid, struct user_regs_struct &regs);
bool set_regs(int pid, struct user_regs_struct &regs);
std::string get_addr_mem_region(const std::vector<lsplt::MapInfo> &map_info, uintptr_t addr);
void *find_module_base(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
void *find_func_addr(const std::vector<lsplt::MapInfo> &local_map_info, const std::vector<lsplt::MapInfo> &remote_map_info,
std::string_view module_name, std::string_view function_name);
void align_stack(struct user_regs_struct &regs, uintptr_t preserve_bytes = 0);
uintptr_t push_memory(int pid, struct user_regs_struct &regs, const void *data, size_t length);
uintptr_t push_string(int pid, struct user_regs_struct &regs, const char *str);
uintptr_t remote_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &args);
bool remote_pre_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr, std::vector<uintptr_t> &args);
uintptr_t remote_post_call(int pid, struct user_regs_struct &regs, uintptr_t expected_return_addr);
int fork_dont_care();
bool wait_for_trace(int pid, int *status, int flags);
std::string parse_status(int status);
std::string get_program(int pid);
void *find_module_return_addr(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
bool switch_mnt_ns(int pid, int *fd);
std::vector<std::string> get_cmdline(int pid);
std::string parse_exec(int pid);
bool skip_syscall(int pid);
bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0, uintptr_t arg3 = 0,
uintptr_t arg4 = 0, uintptr_t arg5 = 0);
uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset);
bool remote_munmap(int pid, uintptr_t addr, size_t size);
int remote_open(int pid, uintptr_t path_addr, int flags);
bool remote_close(int pid, int fd);
int wait_for_child(int pid);
int get_elf_class(std::string_view path);
constexpr size_t kMainMagicLength = 16;
std::string generateMagic(size_t length);
int setfilecon(const char *file_path, const char *security_context);
class UniqueFd {
using Fd = int;
public:
UniqueFd() = default;
UniqueFd(Fd fd) : fd_(fd) {}
~UniqueFd() {
if (fd_ >= 0)
close(fd_);
}
UniqueFd(const UniqueFd &) = delete;
UniqueFd &operator=(const UniqueFd &) = delete;
UniqueFd(UniqueFd &&other) {
std::swap(fd_, other.fd_);
}
UniqueFd &operator=(UniqueFd &&other) {
std::swap(fd_, other.fd_);
return *this;
}
operator const Fd &() const {
return fd_;
}
private:
Fd fd_ = -1;
};
bool set_sockcreate_con(const char *security_context);
#define WPTEVENT(x) (x >> 16)
#define CASE_CONST_RETURN(x) \
case x: \
return #x;
inline const char *parse_ptrace_event(int status) {
status = status >> 16;
switch (status) {
CASE_CONST_RETURN(PTRACE_EVENT_FORK)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK)
CASE_CONST_RETURN(PTRACE_EVENT_CLONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXEC)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXIT)
CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP)
CASE_CONST_RETURN(PTRACE_EVENT_STOP)
default:
return "(no event)";
}
}
inline const char *sigabbrev_np(int sig) {
if (sig > 0 && sig < NSIG)
return sys_signame[sig];
return "(unknown)";
}
@@ -0,0 +1,33 @@
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <android/log.h>
#include <cerrno>
#include <cstring>
#include <string>
#ifndef LOG_TAG
# define LOG_TAG "TEESimulator"
#endif
#ifndef NDEBUG
# define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
# define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#else
# define LOGD(...) (void)0
# define LOGV(...) (void)0
#endif
#define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
namespace logging {
void setPrintEnabled(bool print);
[[gnu::format(printf, 3, 4)]]
void log(int prio, const char *tag, const char *fmt, ...);
} // namespace logging
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <android/log.h>
#include <cstdio>
#include <string>
#include <unistd.h>
#include "logging.hpp"
namespace logging {
static bool use_print = false;
static char prio_str[] = {'V', 'D', 'I', 'W', 'E', 'F'};
void setPrintEnabled(bool print) {
use_print = print;
}
void log(int prio, const char *tag, const char *fmt, ...) {
{
va_list ap;
va_start(ap, fmt);
__android_log_vprint(prio, tag, fmt, ap);
va_end(ap);
}
if (use_print) {
char buf[BUFSIZ];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
auto prio_char = (prio > ANDROID_LOG_DEFAULT && prio <= ANDROID_LOG_FATAL) ? prio_str[prio - ANDROID_LOG_VERBOSE] : '?';
printf("[%c][%d:%d][%s]:%s\n", prio_char, getpid(), gettid(), tag, buf);
}
}
} // namespace logging
+3 -5
View File
@@ -1,16 +1,14 @@
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "binder/Binder.h" #include "binder/Binder.h"
#include "binder/BpBinder.h" #include "binder/BpBinder.h"
#include "binder/IInterface.h"
#include "binder/IPCThreadState.h" #include "binder/IPCThreadState.h"
#include "binder/IServiceManager.h" #include "binder/IServiceManager.h"
#include "binder/RpcSession.h"
#include "binder/Status.h" #include "binder/Status.h"
namespace android { namespace android {
IInterface::IInterface() {}
IInterface::~IInterface() {}
IBinder::IBinder() {} IBinder::IBinder() {}
IBinder::~IBinder() {} IBinder::~IBinder() {}
sp<IInterface> IBinder::queryLocalInterface(const String16 &) { sp<IInterface> IBinder::queryLocalInterface(const String16 &) {
+3 -3
View File
@@ -1,6 +1,8 @@
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "utils/RefBase.h" #include "utils/RefBase.h"
#include "utils/String16.h" #include "utils/String16.h"
#include "utils/String8.h"
#include "utils/StrongPointer.h" #include "utils/StrongPointer.h"
namespace android { namespace android {
@@ -48,8 +50,6 @@ bool RefBase::weakref_type::attemptIncWeak(const void *id) {
void sp_report_race() {} void sp_report_race() {}
String8::String8() {}
String16::String16() {} String16::String16() {}
String16::String16(const String16 &o) {} String16::String16(const String16 &o) {}
-76
View File
@@ -1,76 +0,0 @@
// Fork-based supervisor for instant daemon restart
#include <unistd.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
static volatile sig_atomic_t should_exit = 0;
static void signal_handler(int sig) {
should_exit = 1;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <daemon> [args...]\n", argv[0]);
return 1;
}
// Forward termination signals to exit cleanly
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
const char *daemon_path = argv[1];
char **daemon_argv = &argv[1];
int backoff_ms = 500;
while (!should_exit) {
struct timespec child_start;
clock_gettime(CLOCK_MONOTONIC, &child_start);
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
usleep(100000); // 100ms backoff on fork failure
continue;
}
if (pid == 0) {
// Child: become the daemon
prctl(PR_SET_PDEATHSIG, SIGKILL); // Die if parent dies
setpriority(PRIO_PROCESS, 0, 10); // lower CPU priority than foreground
execv(daemon_path, daemon_argv);
perror("execv failed");
_exit(127);
}
// Parent: wait for child to exit
int status;
waitpid(pid, &status, 0);
if (should_exit) break;
// Exponential backoff on rapid crashes, reset if child was stable
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
long lived_ms = (now.tv_sec - child_start.tv_sec) * 1000 +
(now.tv_nsec - child_start.tv_nsec) / 1000000;
if (lived_ms > 30000) {
backoff_ms = 500;
} else {
usleep(backoff_ms * 1000);
if (backoff_ms < 30000) backoff_ms *= 2;
}
}
return 0;
}
@@ -0,0 +1,239 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import android.content.pm.IPackageManager
import android.content.pm.PackageManager
import android.os.Build
import android.os.ServiceManager
import android.os.SystemProperties
import io.github.beakthoven.TrickyStoreOSS.AttestUtils.CachedAttestData
import io.github.beakthoven.TrickyStoreOSS.config.CustomPatchLevel
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.security.MessageDigest
import java.util.concurrent.ThreadLocalRandom
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
object AndroidUtils {
val bootKey: ByteArray by lazy { randomBytes() }
fun setupBootHash() {
getBootHashFromProp()?.also {
Logger.d("Using boot hash from system property: ${it.toHex()}")
}
?: getBootHashFromAttestation()?.also {
Logger.d("Using boot hash from attestation: ${it.toHex()}")
setBootHashProp(it)
}
?: randomBytes().also {
Logger.d("Generating random boot hash: ${it.toHex()}")
setBootHashProp(it)
}
}
@OptIn(ExperimentalStdlibApi::class)
fun getBootHashFromProp(): ByteArray? {
val digest = SystemProperties.get("ro.boot.vbmeta.digest", null) ?: return null
Logger.d("System property ro.boot.vbmeta.digest: $digest")
if (digest.isBlank()) {
Logger.d("Property is blank")
return null
}
return if (digest.length == 64) digest.hexToByteArray() else null
}
private fun getBootHashFromAttestation(): ByteArray? {
return try {
CachedAttestData?.verifiedBootHash
} catch (e: Exception) {
Logger.e("Failed to get boot hash from attestation: ${e.message}")
null
}
}
private fun setBootHashProp(bytes: ByteArray) {
val hex = bytes.toHex()
try {
Logger.d("Setting ro.boot.vbmeta.digest to: $hex")
SystemProperties.set("ro.boot.vbmeta.digest", hex)
} catch (e: Exception) {
Logger.e("Exception setting vbmeta digest: ${e.message}")
}
}
private fun randomBytes(): ByteArray =
ByteArray(32).also { ThreadLocalRandom.current().nextBytes(it) }
val patchLevel: Int
get() =
getCustomPatchLevel("system", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val patchLevelLong: Int
get() =
getCustomPatchLevel("system", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
val vendorPatchLevel: Int
get() =
getCustomPatchLevel("vendor", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val vendorPatchLevelLong: Int
get() =
getCustomPatchLevel("vendor", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
val bootPatchLevel: Int
get() =
getCustomPatchLevel("boot", false)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(false)
val bootPatchLevelLong: Int
get() =
getCustomPatchLevel("boot", true)
?: Build.VERSION.SECURITY_PATCH.convertPatchLevel(true)
private val customPatchLevel: CustomPatchLevel?
get() = PkgConfig._customPatchLevel
private fun getCustomPatchLevel(component: String, isLong: Boolean): Int? {
val config = customPatchLevel ?: return null
val value =
when (component) {
"system" -> config.system ?: config.all
"vendor" -> config.vendor ?: config.all
"boot" -> config.boot ?: config.all
else -> config.all
} ?: return null
when {
value.equals("no", ignoreCase = true) -> return null
value.equals("prop", ignoreCase = true) -> return null
}
return parsePatchLevelValue(value, component, isLong)
}
private fun parsePatchLevelValue(value: String, component: String, isLong: Boolean): Int? {
val normalized = value.replace("-", "")
return try {
when (normalized.length) {
8 -> {
val year = normalized.substring(0, 4).toInt()
val month = normalized.substring(4, 6).toInt()
val day = normalized.substring(6, 8).toInt()
if (isLong) year * 10000 + month * 100 + day else year * 100 + month
}
6 -> {
val year = normalized.substring(0, 4).toInt()
val month = normalized.substring(4, 6).toInt()
if (isLong) year * 10000 + month * 100 else year * 100 + month
}
else -> {
Logger.e("Invalid patch level length for $component: $normalized")
null
}
}
} catch (e: NumberFormatException) {
Logger.e("Patch level parse error for $component=$value", e)
null
}
}
private val osVersionMap =
mapOf(
Build.VERSION_CODES.BAKLAVA to 160000,
Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
Build.VERSION_CODES.TIRAMISU to 130000,
Build.VERSION_CODES.S_V2 to 120100,
Build.VERSION_CODES.S to 120000,
Build.VERSION_CODES.R to 110000,
Build.VERSION_CODES.Q to 100000,
)
val osVersion: Int
get() = CachedAttestData?.osVersion ?: osVersionMap[Build.VERSION.SDK_INT] ?: 160000
private val attestVersionMap =
mapOf(
Build.VERSION_CODES.Q to 4, // Keymaster 4.1
Build.VERSION_CODES.R to 4, // Keymaster 4.1
Build.VERSION_CODES.S to 100, // KeyMint 1.0
Build.VERSION_CODES.S_V2 to 100, // KeyMint 1.0
Build.VERSION_CODES.TIRAMISU to 200, // KeyMint 2.0
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 300, // KeyMint 3.0
Build.VERSION_CODES.VANILLA_ICE_CREAM to 300, // KeyMint 3.0
Build.VERSION_CODES.BAKLAVA to 400, // KeyMint 4.0
)
val attestVersion: Int
get() = CachedAttestData?.attestVersion ?: attestVersionMap[Build.VERSION.SDK_INT] ?: 400
val keymasterVersion: Int
get() = CachedAttestData?.keymasterVersion ?: if (attestVersion == 4) 41 else attestVersion
fun String.convertPatchLevel(isLong: Boolean): Int =
runCatching {
val parts = split("-")
when {
isLong && parts.size >= 3 ->
parts[0].toInt() * 10000 + parts[1].toInt() * 100 + parts[2].toInt()
parts.size >= 2 -> parts[0].toInt() * 100 + parts[1].toInt()
else -> throw IllegalArgumentException("Invalid patch level format: $this")
}
}
.onFailure { Logger.e("Invalid patch level format: $this", it) }
.getOrDefault(202404)
val apexInfos: List<Pair<String, Long>> by lazy {
runCatching {
val packageManager =
IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
val packages =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getInstalledPackages(PackageManager.MATCH_APEX.toLong(), 0)
} else {
@Suppress("DEPRECATION")
packageManager.getInstalledPackages(PackageManager.MATCH_APEX, 0)
}
packages.list.map { it.packageName to it.longVersionCode }.sortedBy { it.first }
}
.getOrElse {
Logger.e("Failed to get APEX package information")
emptyList()
}
}
val moduleHash: ByteArray by lazy {
runCatching {
val encodables =
apexInfos.flatMap { (packageName, versionCode) ->
listOf(DEROctetString(packageName.toByteArray()), ASN1Integer(versionCode))
}
val sequence = DERSequence(encodables.toTypedArray())
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
}
.getOrElse {
Logger.e("Failed to compute module hash", it)
ByteArray(32)
}
}
}
fun String.trimLine(): String = trim().split("\n").joinToString("\n") { it.trim() }
fun ByteArray.toHex(): String = joinToString("") { "%02x".format(it) }
@@ -0,0 +1,156 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.security.spec.ECGenParameterSpec
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.ASN1OctetString
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.ASN1TaggedObject
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.cert.X509CertificateHolder
val ATTESTATION_OID = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
object AttestUtils {
data class AttestationData(
val verifiedBootHash: ByteArray?,
val attestVersion: Int?,
val keymasterVersion: Int?,
val osVersion: Int?,
)
val TEEStatus: Boolean by lazy { isTEEWorking() }
val CachedAttestData: AttestationData? by lazy { getAttestData() }
private val keygen_alias = "TEESimulator_attest"
private fun isTEEWorking(): Boolean {
return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
android.app.ActivityThread.initializeMainlineModules()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
android.security.keystore2.AndroidKeyStoreProvider.install()
} else {
android.security.keystore.AndroidKeyStoreProvider.install()
}
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val keyPairGenerator =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
val challenge = ByteArray(16).apply { SecureRandom().nextBytes(this) }
val parameterSpec =
KeyGenParameterSpec.Builder(keygen_alias, KeyProperties.PURPOSE_SIGN)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(challenge)
.setIsStrongBoxBacked(false)
.build()
keyPairGenerator.initialize(parameterSpec)
keyPairGenerator.generateKeyPair()
Logger.d("TEE check: successful")
// keyStore.deleteEntry(keygen_alias)
true
} catch (e: Exception) {
Logger.w("TEE check failure: ${e.message}")
false
}
}
private fun getAttestCert(): X509Certificate? {
return if (TEEStatus) {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val certChain = keyStore.getCertificateChain(keygen_alias)
if (certChain == null || certChain.isEmpty()) {
null
} else {
keyStore.deleteEntry(keygen_alias)
certChain[0] as X509Certificate
}
} else {
null
}
}
private fun getAttestData(): AttestationData? {
val leaf: X509Certificate = getAttestCert() ?: return null
return try {
val leafHolder = X509CertificateHolder(leaf.encoded)
val ext: Extension =
leafHolder.getExtension(ATTESTATION_OID)
?: run {
Logger.i("No attestation extension found on certificate")
return null
}
val keyDescriptionSeq = ASN1Sequence.getInstance(ext.extnValue.octets)
val encodables = keyDescriptionSeq.toArray()
val attestVersion = ASN1Integer.getInstance(encodables[0]).value.toInt()
val keymasterVersion = ASN1Integer.getInstance(encodables[2]).value.toInt()
var attestVerifiedBootHash: ByteArray? = null
var attestOSVersion: Int? = null
val teeEnforced = ASN1Sequence.getInstance(encodables[7])
teeEnforced.forEach { element ->
val tagged = element as ASN1TaggedObject
when (tagged.tagNo) {
704 -> { // Parse Root of Trust
val rootOfTrustSeq =
ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
if (rootOfTrustSeq.size() >= 4) {
attestVerifiedBootHash =
ASN1OctetString.getInstance(rootOfTrustSeq.getObjectAt(3)).octets
}
}
705 -> { // Parse OS Version
attestOSVersion =
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
.value
.toInt()
}
}
}
Logger.i("Extracted attestationVersion: $attestVersion")
Logger.i("Extracted keymasterVersion: $keymasterVersion")
Logger.i("Extracted verifiedBootHash: ${attestVerifiedBootHash?.toHex() ?: 0}")
Logger.i("Extracted osVersion: $attestOSVersion")
AttestationData(
verifiedBootHash = attestVerifiedBootHash,
attestVersion = attestVersion,
keymasterVersion = keymasterVersion,
osVersion = attestOSVersion,
)
} catch (e: Exception) {
Logger.e("Failed to parse attestation data", e)
null
}
}
}
@@ -0,0 +1,555 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import android.content.pm.PackageManager
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.EcCurve
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.Tag
import android.os.Build
import android.security.keystore.KeyProperties
import android.system.keystore2.KeyDescriptor
import android.util.Pair
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.interceptors.SecurityLevelInterceptor
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.MessageDigest
import java.security.Security
import java.security.cert.Certificate
import java.security.cert.X509Certificate
import java.security.spec.ECGenParameterSpec
import java.security.spec.RSAKeyGenParameterSpec
import java.util.Date
import javax.security.auth.x500.X500Principal
import org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1Enumerated
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1OctetString
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.DERSet
import org.bouncycastle.asn1.DERTaggedObject
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.KeyUsage
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.PEMKeyPair
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
object CertificateGen {
data class KeyBox(
val pemKeyPair: PEMKeyPair,
val keyPair: KeyPair,
val certificates: List<Certificate>,
)
private data class Digest(val digest: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Digest
return digest.contentEquals(other.digest)
}
override fun hashCode(): Int = digest.contentHashCode()
}
data class KeyGenParameters(
var keySize: Int = 0,
var algorithm: Int = 0,
var certificateSerial: BigInteger? = null,
var certificateNotBefore: Date? = null,
var certificateNotAfter: Date? = null,
var certificateSubject: X500Name? = null,
var rsaPublicExponent: BigInteger? = null,
var ecCurve: Int = 0,
var ecCurveName: String? = null,
var purpose: MutableList<Int> = mutableListOf(),
var digest: MutableList<Int> = mutableListOf(),
var attestationChallenge: ByteArray? = null,
var brand: ByteArray? = null,
var device: ByteArray? = null,
var product: ByteArray? = null,
var manufacturer: ByteArray? = null,
var model: ByteArray? = null,
var imei1: ByteArray? = null,
var imei2: ByteArray? = null,
var meid: ByteArray? = null,
var serialno: ByteArray? = null,
) {
constructor(params: Array<KeyParameter>) : this() {
params.forEach { param ->
Logger.d("Processing key parameter: ${param.tag}")
val value = param.value
when (param.tag) {
Tag.KEY_SIZE -> keySize = value.integer
Tag.ALGORITHM -> algorithm = value.algorithm
Tag.CERTIFICATE_SERIAL -> certificateSerial = BigInteger(value.blob)
Tag.CERTIFICATE_NOT_BEFORE -> certificateNotBefore = Date(value.dateTime)
Tag.CERTIFICATE_NOT_AFTER -> certificateNotAfter = Date(value.dateTime)
Tag.CERTIFICATE_SUBJECT ->
certificateSubject = X500Name(X500Principal(value.blob).name)
Tag.RSA_PUBLIC_EXPONENT -> rsaPublicExponent = BigInteger(value.blob)
Tag.EC_CURVE -> {
ecCurve = value.ecCurve
ecCurveName = getEcCurveName(ecCurve)
}
Tag.PURPOSE -> purpose.add(value.keyPurpose)
Tag.DIGEST -> digest.add(value.digest)
Tag.ATTESTATION_CHALLENGE -> attestationChallenge = value.blob
Tag.ATTESTATION_ID_BRAND -> brand = value.blob
Tag.ATTESTATION_ID_DEVICE -> device = value.blob
Tag.ATTESTATION_ID_PRODUCT -> product = value.blob
Tag.ATTESTATION_ID_MANUFACTURER -> manufacturer = value.blob
Tag.ATTESTATION_ID_MODEL -> model = value.blob
Tag.ATTESTATION_ID_IMEI -> imei1 = value.blob
Tag.ATTESTATION_ID_SECOND_IMEI -> imei2 = value.blob
Tag.ATTESTATION_ID_MEID -> meid = value.blob
}
}
// Fallback: if no EC curve tag but we know key size
if (ecCurveName == null && keySize != 0) {
ecCurveName = ecCurveMapKeySize(keySize)
}
}
private fun ecCurveMapKeySize(curveSize: Int): String =
when (curveSize) {
224 -> "secp224r1"
256 -> "secp256r1"
384 -> "secp384r1"
521 -> "secp521r1"
else -> "secp256r1" // default fallback
}
private fun getEcCurveName(curve: Int): String =
when (curve) {
EcCurve.CURVE_25519 -> "CURVE_25519"
EcCurve.P_224 -> "secp224r1"
EcCurve.P_256 -> "secp256r1"
EcCurve.P_384 -> "secp384r1"
EcCurve.P_521 -> "secp521r1"
else -> throw IllegalArgumentException("Unknown EC curve: $curve")
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as KeyGenParameters
return keySize == other.keySize &&
algorithm == other.algorithm &&
certificateSerial == other.certificateSerial &&
certificateNotBefore == other.certificateNotBefore &&
certificateNotAfter == other.certificateNotAfter &&
certificateSubject == other.certificateSubject &&
rsaPublicExponent == other.rsaPublicExponent &&
ecCurve == other.ecCurve &&
ecCurveName == other.ecCurveName &&
purpose == other.purpose &&
digest == other.digest &&
attestationChallenge.contentEquals(other.attestationChallenge) &&
brand.contentEquals(other.brand) &&
device.contentEquals(other.device) &&
product.contentEquals(other.product) &&
manufacturer.contentEquals(other.manufacturer) &&
model.contentEquals(other.model) &&
imei1.contentEquals(other.imei1) &&
imei2.contentEquals(other.imei2) &&
meid.contentEquals(other.meid) &&
serialno.contentEquals(other.serialno)
}
}
fun generateChain(
uid: Int,
params: KeyGenParameters,
keyPair: KeyPair,
securityLevel: Int = 1,
): List<ByteArray>? =
runCatching {
val keybox = getKeyboxForAlgorithm(uid, params.algorithm) ?: return null
val issuer = X509CertificateHolder(keybox.certificates[0].encoded).subject
val leaf = buildCertificate(keyPair, keybox, params, issuer, uid, securityLevel)
val chain = buildList {
add(leaf)
addAll(keybox.certificates)
}
CertificateUtils.run { chain.toByteArrayList() }
}
.onFailure { Logger.e("Failed to generate certificate chain", it) }
.getOrNull()
fun generateKeyPair(params: KeyGenParameters): KeyPair? =
runCatching {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider())
val (keyPairGenerator, spec) =
when (params.algorithm) {
Algorithm.EC -> {
Logger.d("Generating EC keypair of size ${params.keySize}")
val spec = ECGenParameterSpec(params.ecCurveName)
val kpg =
KeyPairGenerator.getInstance(
"EC",
BouncyCastleProvider.PROVIDER_NAME,
)
kpg to spec
}
Algorithm.RSA -> {
Logger.d("Generating RSA keypair of size ${params.keySize}")
val spec =
RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent)
val kpg =
KeyPairGenerator.getInstance(
"RSA",
BouncyCastleProvider.PROVIDER_NAME,
)
kpg to spec
}
else -> {
throw IllegalArgumentException(
"Unsupported algorithm: ${params.algorithm}"
)
}
}
keyPairGenerator.initialize(spec)
keyPairGenerator.generateKeyPair()
}
.onFailure { Logger.e("Failed to generate key pair", it) }
.getOrNull()
fun generateKeyPair(
uid: Int,
descriptor: KeyDescriptor,
attestKeyDescriptor: KeyDescriptor?,
params: KeyGenParameters,
securityLevel: Int = 1,
): Pair<KeyPair, List<Certificate>>? =
runCatching {
Logger.i("Requested KeyPair with alias: ${descriptor.alias}")
val hasAttestKey = attestKeyDescriptor != null
if (hasAttestKey) {
Logger.i("Requested KeyPair with attestKey: ${attestKeyDescriptor?.alias}")
}
val keyPair = generateKeyPair(params) ?: return null
val keybox = getKeyboxForAlgorithm(uid, params.algorithm) ?: return null
val (signingKeyPair, issuer) =
if (hasAttestKey) {
getAttestationKeyInfo(uid, attestKeyDescriptor!!)?.let {
it.first to it.second
}
?: (keybox.keyPair to
X509CertificateHolder(keybox.certificates[0].encoded).subject)
} else {
keybox.keyPair to
X509CertificateHolder(keybox.certificates[0].encoded).subject
}
val leaf =
buildCertificate(
keyPair,
keybox,
params,
issuer,
uid,
securityLevel,
signingKeyPair,
)
val chain = buildList {
add(leaf)
if (!hasAttestKey) {
addAll(keybox.certificates)
}
}
Logger.d("Successfully generated certificate for alias: ${descriptor.alias}")
Pair(keyPair, chain)
}
.onFailure { Logger.e("Failed to generate key pair with certificates", it) }
.getOrNull()
private fun mapAlgorithmToName(algorithm: Int): String? =
when (algorithm) {
Algorithm.EC -> KeyProperties.KEY_ALGORITHM_EC
Algorithm.RSA -> KeyProperties.KEY_ALGORITHM_RSA
else -> {
Logger.e("Unsupported algorithm: $algorithm")
null
}
}
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox? {
val algorithmName = mapAlgorithmToName(algorithm) ?: return null
val keyboxFileName = PkgConfig.getKeyboxFileForUid(uid)
return KeyBoxUtils.getKeybox(keyboxFileName, algorithmName)
}
private fun getAttestationKeyInfo(
uid: Int,
attestKeyDescriptor: KeyDescriptor,
): Pair<KeyPair, X500Name>? {
Logger.d("Looking for attestation key: uid=$uid alias=${attestKeyDescriptor.alias}")
val keyInfo = SecurityLevelInterceptor.getKeyPairs(uid, attestKeyDescriptor.alias)
return if (keyInfo != null) {
val issuer = X509CertificateHolder(keyInfo.second[0].encoded).subject
Pair(keyInfo.first, issuer)
} else {
Logger.e("Attestation key info not found, falling back to default keybox")
null
}
}
private fun buildCertificate(
keyPair: KeyPair,
keybox: KeyBox,
params: KeyGenParameters,
issuer: X500Name,
uid: Int,
securityLevel: Int = 1,
signingKeyPair: KeyPair = keybox.keyPair,
): Certificate {
val builder =
JcaX509v3CertificateBuilder(
issuer,
params.certificateSerial ?: BigInteger.ONE,
params.certificateNotBefore ?: Date(),
params.certificateNotAfter ?: (keybox.certificates[0] as X509Certificate).notAfter,
params.certificateSubject ?: X500Name("CN=Android KeyStore Key"),
keyPair.public,
)
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
builder.addExtension(buildAttestExtension(params, uid, securityLevel))
val signerAlgorithm =
when (params.algorithm) {
Algorithm.EC -> "SHA256withECDSA"
Algorithm.RSA -> "SHA256withRSA"
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
}
val contentSigner = JcaContentSignerBuilder(signerAlgorithm).build(signingKeyPair.private)
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
}
private fun buildAttestExtension(
params: KeyGenParameters,
uid: Int,
securityLevel: Int = 1,
): Extension {
try {
val key = AndroidUtils.bootKey
val hash = AndroidUtils.getBootHashFromProp()
Logger.d("Using boothash ${hash?.toHex() ?: 0}")
val rootOfTrustEncodables =
arrayOf(
DEROctetString(key),
ASN1Boolean.TRUE,
ASN1Enumerated(0),
DEROctetString(hash),
)
val rootOfTrustSeq = DERSequence(rootOfTrustEncodables)
val purpose = DERSet(params.purpose.map { ASN1Integer(it.toLong()) }.toTypedArray())
val algorithm = ASN1Integer(params.algorithm.toLong())
val keySize = ASN1Integer(params.keySize.toLong())
val digest = DERSet(params.digest.map { ASN1Integer(it.toLong()) }.toTypedArray())
val ecCurve = ASN1Integer(params.ecCurve.toLong())
val noAuthRequired = DERNull.INSTANCE
val osVersion = ASN1Integer(AndroidUtils.osVersion.toLong())
val osPatchLevel = ASN1Integer(AndroidUtils.patchLevel.toLong())
val applicationID = createApplicationId(uid)
val bootPatchLevel = ASN1Integer(AndroidUtils.bootPatchLevelLong.toLong())
val vendorPatchLevel = ASN1Integer(AndroidUtils.vendorPatchLevelLong.toLong())
val creationDateTime = ASN1Integer(System.currentTimeMillis())
val origin = ASN1Integer(0L)
val moduleHash = DEROctetString(AndroidUtils.moduleHash)
val teeEnforcedObjects =
mutableListOf(
DERTaggedObject(true, 1, purpose),
DERTaggedObject(true, 2, algorithm),
DERTaggedObject(true, 3, keySize),
DERTaggedObject(true, 5, digest),
DERTaggedObject(true, 10, ecCurve),
DERTaggedObject(true, 503, noAuthRequired),
DERTaggedObject(true, 702, origin),
DERTaggedObject(true, 704, rootOfTrustSeq),
DERTaggedObject(true, 705, osVersion),
DERTaggedObject(true, 706, osPatchLevel),
DERTaggedObject(true, 718, vendorPatchLevel),
DERTaggedObject(true, 719, bootPatchLevel),
)
if (AndroidUtils.attestVersion >= 400) {
teeEnforcedObjects.add(DERTaggedObject(true, 724, moduleHash))
}
params.brand?.let {
teeEnforcedObjects.add(DERTaggedObject(true, 710, DEROctetString(it)))
}
params.device?.let {
teeEnforcedObjects.add(DERTaggedObject(true, 711, DEROctetString(it)))
}
params.product?.let {
teeEnforcedObjects.add(DERTaggedObject(true, 712, DEROctetString(it)))
}
params.manufacturer?.let {
teeEnforcedObjects.add(DERTaggedObject(true, 716, DEROctetString(it)))
}
params.model?.let {
teeEnforcedObjects.add(DERTaggedObject(true, 717, DEROctetString(it)))
}
params.serialno?.let {
teeEnforcedObjects.add(DERTaggedObject(true, 713, DEROctetString(it)))
}
params.imei1?.let {
teeEnforcedObjects.add(DERTaggedObject(true, 714, DEROctetString(it)))
}
params.meid?.let {
teeEnforcedObjects.add(DERTaggedObject(true, 715, DEROctetString(it)))
}
if (AndroidUtils.attestVersion >= 300) {
params.imei2?.let {
teeEnforcedObjects.add(DERTaggedObject(true, 723, DEROctetString(it)))
}
}
teeEnforcedObjects.sortBy { it.tagNo }
val softwareEnforcedObjects =
arrayOf<ASN1Encodable>(
DERTaggedObject(true, 709, applicationID),
DERTaggedObject(true, 701, creationDateTime),
)
return Extension(
ATTESTATION_OID,
false,
buildKeyDescriptionOctet(
teeEnforcedObjects.toTypedArray(),
softwareEnforcedObjects,
params,
securityLevel,
),
)
} catch (t: Throwable) {
Logger.e("Failed to create attestation extension", t)
throw t
}
}
private fun buildKeyDescriptionOctet(
teeEnforcedEncodables: Array<ASN1Encodable>,
softwareEnforcedEncodables: Array<ASN1Encodable>,
params: KeyGenParameters,
securityLevel: Int = 1,
): ASN1OctetString {
val attestationVersion = ASN1Integer(AndroidUtils.attestVersion.toLong())
val attestationSecurityLevel = ASN1Enumerated(securityLevel)
val keymasterVersion = ASN1Integer(AndroidUtils.keymasterVersion.toLong())
val keymasterSecurityLevel = ASN1Enumerated(securityLevel)
val attestationChallenge = DEROctetString(params.attestationChallenge ?: ByteArray(0))
val uniqueId = DEROctetString(ByteArray(0))
val softwareEnforced = DERSequence(softwareEnforcedEncodables)
val teeEnforced = DERSequence(teeEnforcedEncodables)
val keyDescriptionEncodables =
arrayOf(
attestationVersion,
attestationSecurityLevel,
keymasterVersion,
keymasterSecurityLevel,
attestationChallenge,
uniqueId,
softwareEnforced,
teeEnforced,
)
val keyDescriptionSeq = DERSequence(keyDescriptionEncodables)
return DEROctetString(keyDescriptionSeq.encoded)
}
@Throws(Throwable::class)
private fun createApplicationId(uid: Int): DEROctetString {
val pm = PkgConfig.getPm() ?: throw IllegalStateException("PackageManager not found!")
val packages =
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
val messageDigest = MessageDigest.getInstance("SHA-256")
val signatures = mutableSetOf<Digest>()
val packageInfos =
packages.map { packageName ->
val info =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.getPackageInfo(
packageName,
PackageManager.GET_SIGNING_CERTIFICATES.toLong(),
uid / 100000,
)
} else {
pm.getPackageInfo(
packageName,
PackageManager.GET_SIGNING_CERTIFICATES,
uid / 100000,
)
}
info.signingInfo?.signingCertificateHistory?.forEach { signature ->
signatures.add(Digest(messageDigest.digest(signature.toByteArray())))
}
info
}
val packageInfoArray =
packageInfos
.map { info ->
DERSequence(
arrayOf(
DEROctetString(info.packageName.toByteArray(StandardCharsets.UTF_8)),
ASN1Integer(info.longVersionCode),
)
)
}
.toTypedArray()
val signaturesArray = signatures.map { DEROctetString(it.digest) }.toTypedArray()
val applicationIdArray = arrayOf(DERSet(packageInfoArray), DERSet(signaturesArray))
return DEROctetString(DERSequence(applicationIdArray).encoded)
}
}
@@ -0,0 +1,266 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.io.ByteArrayInputStream
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.LinkedList
import java.util.concurrent.ConcurrentHashMap
import org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1EncodableVector
import org.bouncycastle.asn1.ASN1Enumerated
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.ASN1TaggedObject
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.DERTaggedObject
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
object CertificateHack {
private val certificateFactory: CertificateFactory by lazy {
try {
CertificateFactory.getInstance("X.509")
} catch (t: Throwable) {
Logger.e("Failed to initialize certificate factory", t)
throw RuntimeException("Cannot initialize certificate factory", t)
}
}
data class KeyIdentifier(val alias: String, val uid: Int)
val leafAlgorithms = ConcurrentHashMap<KeyIdentifier, String>()
fun clearLeafAlgorithms() {
leafAlgorithms.clear()
}
fun hackCertificateChain(certificateChain: Array<Certificate>?, uid: Int): Array<Certificate> {
if (certificateChain == null) {
throw UnsupportedOperationException("Certificate chain is null!")
}
return try {
val leaf =
certificateFactory.generateCertificate(
ByteArrayInputStream(certificateChain[0].encoded)
) as X509Certificate
val extensionBytes =
leaf.getExtensionValue(ATTESTATION_OID.id)
?: return certificateChain // No attestation extension, return original
val leafHolder = X509CertificateHolder(leaf.encoded)
val extension = leafHolder.getExtension(ATTESTATION_OID)
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
val encodables = sequence.toArray()
val teeEnforced = encodables[7] as ASN1Sequence
val vector = ASN1EncodableVector()
var rootOfTrust: ASN1Encodable? = null
teeEnforced.forEach { element ->
val taggedObject = element as ASN1TaggedObject
if (taggedObject.tagNo == 704) {
rootOfTrust = taggedObject.baseObject.toASN1Primitive()
} else {
vector.add(taggedObject)
}
}
val keyboxFileName = PkgConfig.getKeyboxFileForUid(uid)
val algorithmName = leaf.publicKey.algorithm
val keybox =
KeyBoxUtils.getKeybox(keyboxFileName, algorithmName)
?: throw UnsupportedOperationException(
"Unsupported algorithm '$algorithmName' in keybox '$keyboxFileName'"
)
val certificates = LinkedList(keybox.certificates)
val builder =
X509v3CertificateBuilder(
X509CertificateHolder(certificates[0].encoded).subject,
leafHolder.serialNumber,
leafHolder.notBefore,
leafHolder.notAfter,
leafHolder.subject,
leafHolder.subjectPublicKeyInfo,
)
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
val hackedExtension = hackAttestExtension(rootOfTrust, vector, encodables)
builder.addExtension(hackedExtension)
leafHolder.extensions.extensionOIDs.forEach { oid ->
if (oid.id != ATTESTATION_OID.id) {
builder.addExtension(leafHolder.getExtension(oid))
}
}
certificates.addFirst(
JcaX509CertificateConverter().getCertificate(builder.build(signer))
)
certificates.toTypedArray()
} catch (t: Throwable) {
Logger.e("Failed to hack certificate chain for uid=$uid", t)
certificateChain
}
}
fun hackCACertificateChain(caList: ByteArray?, alias: String, uid: Int): ByteArray {
if (caList == null) {
throw UnsupportedOperationException("CA list is null!")
}
return try {
val key = KeyIdentifier(alias, uid)
val algorithm =
leafAlgorithms.remove(key)
?: throw UnsupportedOperationException("No algorithm found for key $key")
val keyboxFileName = PkgConfig.getKeyboxFileForUid(uid)
val keybox =
KeyBoxUtils.getKeybox(keyboxFileName, algorithm)
?: throw UnsupportedOperationException(
"Unsupported algorithm '$algorithm' in keybox '$keyboxFileName'"
)
CertificateUtils.run { keybox.certificates.toByteArray() } ?: caList
} catch (t: Throwable) {
Logger.e("Failed to hack CA certificate chain for uid=$uid", t)
caList
}
}
fun hackUserCertificate(certificate: ByteArray?, alias: String, uid: Int): ByteArray {
if (certificate == null) {
throw UnsupportedOperationException("Leaf certificate is null!")
}
return try {
val leaf =
certificateFactory.generateCertificate(ByteArrayInputStream(certificate))
as X509Certificate
val extensionBytes =
leaf.getExtensionValue(ATTESTATION_OID.id)
?: return certificate // No attestation extension, return original
val keyIdentifier = KeyIdentifier(alias, uid)
leafAlgorithms[keyIdentifier] = leaf.publicKey.algorithm
val leafHolder = X509CertificateHolder(leaf.encoded)
val extension = leafHolder.getExtension(ATTESTATION_OID)
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
val encodables = sequence.toArray()
val teeEnforced = encodables[7] as ASN1Sequence
val vector = ASN1EncodableVector()
var rootOfTrust: ASN1Encodable? = null
teeEnforced.forEach { element ->
val taggedObject = element as ASN1TaggedObject
if (taggedObject.tagNo == 704) {
rootOfTrust = taggedObject.baseObject.toASN1Primitive()
} else {
vector.add(taggedObject)
}
}
val keyboxFileName = PkgConfig.getKeyboxFileForUid(uid)
val algorithmName = leaf.publicKey.algorithm
val keybox =
KeyBoxUtils.getKeybox(keyboxFileName, algorithmName)
?: throw UnsupportedOperationException(
"Unsupported algorithm '$algorithmName' in keybox '$keyboxFileName'"
)
val builder =
X509v3CertificateBuilder(
X509CertificateHolder(keybox.certificates[0].encoded).subject,
leafHolder.serialNumber,
leafHolder.notBefore,
leafHolder.notAfter,
leafHolder.subject,
leafHolder.subjectPublicKeyInfo,
)
val signer = JcaContentSignerBuilder(leaf.sigAlgName).build(keybox.keyPair.private)
val hackedExtension = hackAttestExtension(rootOfTrust, vector, encodables)
builder.addExtension(hackedExtension)
leafHolder.extensions.extensionOIDs.forEach { oid ->
if (oid.id != ATTESTATION_OID.id) {
builder.addExtension(leafHolder.getExtension(oid))
}
}
JcaX509CertificateConverter().getCertificate(builder.build(signer)).encoded
} catch (t: Throwable) {
Logger.e("Failed to hack user certificate for uid=$uid", t)
certificate
}
}
private fun hackAttestExtension(
originalRootOfTrust: ASN1Encodable?,
vector: ASN1EncodableVector,
originalEncodables: Array<ASN1Encodable>,
): Extension {
val verifiedBootKey = AndroidUtils.bootKey
var verifiedBootHash: ByteArray? = null
try {
if (originalRootOfTrust is ASN1Sequence) {
verifiedBootHash =
CertificateUtils.getByteArrayFromAsn1(originalRootOfTrust.getObjectAt(3))
}
} catch (t: Throwable) {
Logger.e("Failed to get verified boot hash from original, using generated", t)
}
if (verifiedBootHash == null) {
verifiedBootHash = AndroidUtils.getBootHashFromProp()
}
val rootOfTrustElements =
arrayOf(
DEROctetString(verifiedBootKey),
ASN1Boolean.TRUE,
ASN1Enumerated(0),
DEROctetString(verifiedBootHash),
)
val hackedRootOfTrust = DERSequence(rootOfTrustElements)
vector.add(
DERTaggedObject(true, 718, ASN1Integer(AndroidUtils.vendorPatchLevelLong.toLong()))
)
vector.add(
DERTaggedObject(true, 719, ASN1Integer(AndroidUtils.bootPatchLevelLong.toLong()))
)
vector.add(DERTaggedObject(true, 706, ASN1Integer(AndroidUtils.patchLevel.toLong())))
vector.add(DERTaggedObject(true, 705, ASN1Integer(AndroidUtils.osVersion.toLong())))
vector.add(DERTaggedObject(704, hackedRootOfTrust))
val hackEnforced = DERSequence(vector)
originalEncodables[7] = hackEnforced
val hackedSequence = DERSequence(originalEncodables)
val hackedSequenceOctets = DEROctetString(hackedSequence)
return Extension(ATTESTATION_OID, false, hackedSequenceOctets)
}
}
@@ -0,0 +1,225 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import android.system.keystore2.KeyEntryResponse
import android.system.keystore2.KeyMetadata
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils.putCertificateChain
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.StringReader
import java.security.KeyPair
import java.security.cert.Certificate
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.CertificateParsingException
import java.security.cert.X509Certificate
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.openssl.PEMKeyPair
import org.bouncycastle.openssl.PEMParser
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import org.bouncycastle.util.io.pem.PemReader
object CertificateUtils {
sealed class CertificateResult<out T> {
data class Success<T>(val data: T) : CertificateResult<T>()
data class Error(val message: String, val cause: Throwable? = null) :
CertificateResult<Nothing>()
inline fun <R> map(transform: (T) -> R): CertificateResult<R> =
when (this) {
is Success -> Success(transform(data))
is Error -> this
}
fun getOrNull(): T? =
when (this) {
is Success -> data
is Error -> null
}
}
sealed class ParseResult<out T> {
data class Success<T>(val data: T) : ParseResult<T>()
data class Error(val message: String, val cause: Throwable? = null) : ParseResult<Nothing>()
}
fun ByteArray?.toCertificate(): X509Certificate? {
return this?.let { bytes ->
try {
val certFactory = CertificateFactory.getInstance("X.509")
certFactory.generateCertificate(ByteArrayInputStream(bytes)) as? X509Certificate
} catch (e: CertificateException) {
Logger.w("Couldn't parse certificate in keystore", e)
null
}
}
}
fun ByteArray.toCertificateResult(): CertificateResult<X509Certificate> {
return try {
val certFactory = CertificateFactory.getInstance("X.509")
val certificate =
certFactory.generateCertificate(ByteArrayInputStream(this)) as X509Certificate
CertificateResult.Success(certificate)
} catch (e: CertificateException) {
CertificateResult.Error("Failed to parse certificate", e)
}
}
@Suppress("UNCHECKED_CAST")
fun ByteArray?.toCertificates(): Collection<X509Certificate> {
return this?.let { bytes ->
try {
val certFactory = CertificateFactory.getInstance("X.509")
certFactory.generateCertificates(ByteArrayInputStream(bytes))
as Collection<X509Certificate>
} catch (e: CertificateException) {
Logger.w("Couldn't parse certificates in keystore", e)
emptyList()
}
} ?: emptyList()
}
fun Collection<Certificate>.toByteArray(): ByteArray? =
runCatching {
ByteArrayOutputStream().use { outputStream ->
forEach { cert -> outputStream.write(cert.encoded) }
outputStream.toByteArray()
}
}
.onFailure { Logger.w("Failed to convert certificates to byte array", it) }
.getOrNull()
fun Collection<Certificate>.toByteArrayList(): List<ByteArray>? =
runCatching { map { it.encoded } }
.onFailure { Logger.w("Failed to convert certificates to byte array list", it) }
.getOrNull()
fun KeyEntryResponse?.getCertificateChain(): Array<Certificate>? {
val metadata = this?.metadata ?: return null
val leafCert = metadata.certificate?.toCertificate() ?: return null
return when (val chainBytes = metadata.certificateChain) {
null -> arrayOf(leafCert)
else -> {
val additionalCerts = chainBytes.toCertificates()
buildList {
add(leafCert)
addAll(additionalCerts)
}
.toTypedArray()
}
}
}
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching { metadata.putCertificateChain(chain) }
}
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching {
if (chain.isEmpty()) return@runCatching
certificate = chain[0].encoded
if (chain.size > 1) {
ByteArrayOutputStream().use { output ->
for (i in 1 until chain.size) {
output.write(chain[i].encoded)
}
certificateChain = output.toByteArray()
}
} else {
certificateChain = null
}
}
}
// Certificate parsing utilities
fun parseKeyPair(keyContent: String): ParseResult<PEMKeyPair> {
return try {
PEMParser(StringReader(keyContent.trimLine())).use { parser ->
val pemObject = parser.readObject()
if (pemObject is PEMKeyPair) {
ParseResult.Success(pemObject)
} else {
ParseResult.Error("Invalid PEM key pair format")
}
}
} catch (t: Throwable) {
ParseResult.Error("Failed to parse PEM key pair", t)
}
}
fun parseCertificate(certContent: String): ParseResult<Certificate> {
return try {
PemReader(StringReader(certContent.trimLine())).use { reader ->
val pemObject = reader.readPemObject()
val certificate =
CertificateFactory.getInstance("X.509")
.generateCertificate(ByteArrayInputStream(pemObject.content))
ParseResult.Success(certificate)
}
} catch (t: Throwable) {
ParseResult.Error("Failed to parse certificate", t)
}
}
fun convertPemToKeyPair(pemKeyPair: PEMKeyPair): KeyPair {
return JcaPEMKeyConverter().getKeyPair(pemKeyPair)
}
@Throws(CertificateParsingException::class)
fun getByteArrayFromAsn1(asn1Encodable: ASN1Encodable): ByteArray {
return when (asn1Encodable) {
is DEROctetString -> asn1Encodable.octets
else ->
throw CertificateParsingException(
"Expected DEROctetString, got ${asn1Encodable::class.simpleName}"
)
}
}
}
fun ByteArray?.toX509Certificate(): X509Certificate? =
CertificateUtils.run { this@toX509Certificate.toCertificate() }
fun ByteArray?.toX509Certificates(): Collection<X509Certificate> =
CertificateUtils.run { this@toX509Certificates.toCertificates() }
fun Collection<Certificate>.encodedBytes(): ByteArray? =
CertificateUtils.run { this@encodedBytes.toByteArray() }
fun Collection<Certificate>.encodedBytesList(): List<ByteArray>? =
CertificateUtils.run { this@encodedBytesList.toByteArrayList() }
fun KeyEntryResponse.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching { metadata.putCertificateChain(chain).getOrThrow() }
}
fun KeyMetadata.putCertificateChain(chain: Array<Certificate>): Result<Unit> {
return runCatching {
if (chain.isEmpty()) return@runCatching
certificate = chain[0].encoded
if (chain.size > 1) {
ByteArrayOutputStream().use { output ->
for (i in 1 until chain.size) {
output.write(chain[i].encoded)
}
certificateChain = output.toByteArray()
}
} else {
certificateChain = null
}
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import android.os.Build
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.interceptors.Keystore2Interceptor
import io.github.beakthoven.TrickyStoreOSS.interceptors.KeystoreInterceptor
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
private const val RETRY_DELAY_MS = 1000L
private const val SERVICE_SLEEP_MS = 1000000L
fun main(args: Array<String>) {
Logger.i("Welcome to TEESimulator!")
try {
AndroidUtils.setupBootHash()
initializeInterceptors()
maintainService()
} catch (e: Exception) {
Logger.e("Fatal error in main", e)
throw e
}
}
private fun initializeInterceptors() {
val interceptor = selectKeystoreInterceptor()
while (!interceptor.tryRunKeystoreInterceptor()) {
Logger.d("Retrying interceptor initialization...")
Thread.sleep(RETRY_DELAY_MS)
}
PkgConfig.initialize()
Logger.i("Interceptors initialized successfully")
}
private fun selectKeystoreInterceptor() =
when {
Build.VERSION.SDK_INT in Build.VERSION_CODES.Q..Build.VERSION_CODES.R -> {
Logger.i("Using KeystoreInterceptor for Android Q/R (SDK ${Build.VERSION.SDK_INT})")
KeystoreInterceptor
}
else -> {
Logger.i("Using Keystore2Interceptor for Android S+ (SDK ${Build.VERSION.SDK_INT})")
Keystore2Interceptor
}
}
private fun maintainService() {
Logger.i("Service started, entering maintenance mode")
while (true) {
Thread.sleep(SERVICE_SLEEP_MS)
}
}
@@ -0,0 +1,363 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS
import android.security.keystore.KeyProperties
import io.github.beakthoven.TrickyStoreOSS.CertificateGen.KeyBox
import io.github.beakthoven.TrickyStoreOSS.CertificateHack.clearLeafAlgorithms
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.io.File
import java.io.IOException
import java.io.StringReader
import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory
class XmlParser(private val xmlContent: String) {
sealed class ParseResult {
data class Success(val attributes: Map<String, String>) : ParseResult()
data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
}
fun obtainPath(path: String): ParseResult {
return try {
val factory = XmlPullParserFactory.newInstance()
val parser = factory.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(StringReader(xmlContent))
val tags = path.split(".").toTypedArray()
val result = readData(parser, tags, 0, mutableMapOf())
ParseResult.Success(result)
} catch (e: XmlPullParserException) {
ParseResult.Error("XML parsing error: ${e.message}", e)
} catch (e: IOException) {
ParseResult.Error("IO error while parsing XML: ${e.message}", e)
} catch (e: Exception) {
ParseResult.Error("Unexpected error: ${e.message}", e)
}
}
@Throws(Exception::class)
fun obtainPathLegacy(path: String): Map<String, String> {
when (val result = obtainPath(path)) {
is ParseResult.Success -> return result.attributes
is ParseResult.Error -> throw result.cause ?: Exception(result.message)
}
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readData(
parser: XmlPullParser,
tags: Array<String>,
index: Int,
tagCounts: MutableMap<String, Int>,
): Map<String, String> {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
val currentTag = parser.name ?: continue
val targetTag = tags[index]
val tagParts = targetTag.split("[")
val baseTagName = tagParts[0]
if (currentTag == baseTagName) {
return if (tagParts.size > 1) {
handleIndexedTag(parser, tags, index, tagCounts, currentTag, tagParts[1])
} else {
handleRegularTag(parser, tags, index)
}
} else {
skipCurrentElement(parser)
}
}
throw XmlPullParserException("Path not found: ${tags.joinToString(".")}")
}
@Throws(IOException::class, XmlPullParserException::class)
private fun handleIndexedTag(
parser: XmlPullParser,
tags: Array<String>,
index: Int,
tagCounts: MutableMap<String, Int>,
currentTag: String,
indexPart: String,
): Map<String, String> {
val targetIndex =
indexPart.replace("]", "").toIntOrNull()
?: throw XmlPullParserException("Invalid index in tag: $indexPart")
val currentCount = tagCounts.getOrDefault(currentTag, 0)
return if (currentCount < targetIndex) {
tagCounts[currentTag] = currentCount + 1
readData(parser, tags, index, tagCounts)
} else {
if (index == tags.size - 1) {
readAttributes(parser)
} else {
readData(parser, tags, index + 1, tagCounts)
}
}
}
@Throws(IOException::class, XmlPullParserException::class)
private fun handleRegularTag(
parser: XmlPullParser,
tags: Array<String>,
index: Int,
): Map<String, String> {
return if (index == tags.size - 1) {
readAttributes(parser)
} else {
readData(parser, tags, index + 1, mutableMapOf())
}
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readAttributes(parser: XmlPullParser): Map<String, String> {
val attributes = mutableMapOf<String, String>()
for (i in 0 until parser.attributeCount) {
val name = parser.getAttributeName(i)
val value = parser.getAttributeValue(i)
if (name != null && value != null) {
attributes[name] = value
}
}
if (parser.next() == XmlPullParser.TEXT) {
parser.text?.let { text -> attributes["text"] = text }
}
return attributes
}
@Throws(XmlPullParserException::class, IOException::class)
private fun skipCurrentElement(parser: XmlPullParser) {
if (parser.eventType != XmlPullParser.START_TAG) {
throw IllegalStateException("Parser must be positioned at START_TAG")
}
var depth = 1
while (depth != 0) {
when (parser.next()) {
XmlPullParser.END_TAG -> depth--
XmlPullParser.START_TAG -> depth++
}
}
}
}
object KeyBoxUtils {
private val loadedKeyboxFiles = ConcurrentHashMap<String, ConcurrentHashMap<String, KeyBox>>()
/**
* The primary public function to get a specific KeyBox for a given algorithm and file. It will
* load and cache the file on demand if it hasn't been seen before.
*
* @param keyboxFileName The simple name of the keybox file (e.g., "keybox.xml").
* @param algorithm The algorithm key (e.g., KeyProperties.KEY_ALGORITHM_EC).
* @return The requested KeyBox, or null if not found in the specified file.
*/
fun getKeybox(keyboxFileName: String, algorithm: String): KeyBox? {
val keyboxesForFile =
loadedKeyboxFiles.getOrPut(keyboxFileName) {
// If this file is not in our cache, load it now.
readFromFile(keyboxFileName)
}
Logger.i("Retriving keybox $keyboxFileName [$algorithm]")
return keyboxesForFile[algorithm]
}
private fun readFromFile(fileName: String): ConcurrentHashMap<String, KeyBox> {
val filePath = File(PkgConfig.CONFIG_PATH, fileName)
Logger.i("Loading keybox file: $filePath")
val keyboxes = ConcurrentHashMap<String, KeyBox>()
if (!filePath.exists()) {
Logger.e("Keybox file not found: $filePath")
return keyboxes // Return an empty map if file doesn't exist
}
try {
val xmlData = filePath.readText()
val xmlParser = XmlParser(xmlData.sanitizeXml())
val numberOfKeyboxesResult = xmlParser.obtainPath("AndroidAttestation.NumberOfKeyboxes")
val numberOfKeyboxes =
when (numberOfKeyboxesResult) {
is XmlParser.ParseResult.Success ->
numberOfKeyboxesResult.attributes["text"]?.toIntOrNull() ?: 1
is XmlParser.ParseResult.Error ->
throw Exception(
numberOfKeyboxesResult.message,
numberOfKeyboxesResult.cause,
)
}
repeat(numberOfKeyboxes) { i ->
val (algorithmName, keyBox) = processKeybox(xmlParser, i)
keyboxes[algorithmName] = keyBox
}
Logger.i("Successfully loaded ${keyboxes.size} keyboxes from $fileName")
} catch (t: Throwable) {
Logger.e("Error loading XML file ($fileName)", t)
}
return keyboxes
}
fun hasKeyboxes(): Boolean =
loadedKeyboxFiles.isNotEmpty() && loadedKeyboxFiles.values.any { it.isNotEmpty() }
// This function is now deprecated and should be removed. We keep it for now to show the
// transition.
// Its logic is now inside readFromFile.
@Deprecated("Use getKeybox(fileName, algorithm) instead for dynamic loading.")
fun readFromXml(xmlData: String?) {
// The old global state is gone. This function's logic is now in readFromFile.
// We could make this load into a default keybox for backward compatibility if needed.
loadedKeyboxFiles.clear()
clearLeafAlgorithms()
if (xmlData == null) {
Logger.i("Clearing all keyboxes")
return
}
try {
val xmlParser = XmlParser(xmlData.sanitizeXml())
val numberOfKeyboxesResult = xmlParser.obtainPath("AndroidAttestation.NumberOfKeyboxes")
val numberOfKeyboxes =
when (numberOfKeyboxesResult) {
is XmlParser.ParseResult.Success ->
numberOfKeyboxesResult.attributes["text"]?.toIntOrNull()
?: throw IllegalArgumentException("Invalid number of keyboxes")
is XmlParser.ParseResult.Error ->
throw Exception(
numberOfKeyboxesResult.message,
numberOfKeyboxesResult.cause,
)
}
repeat(numberOfKeyboxes) { i -> processKeybox(xmlParser, i) }
Logger.i("Successfully updated $numberOfKeyboxes keyboxes")
} catch (t: Throwable) {
Logger.e("Error loading XML file (keyboxes cleared)", t)
}
}
private fun String.sanitizeXml(): String {
var content = this
val boms = listOf("\uFEFF", "\uFFFE", "\u0000\uFEFF")
content = content.trimStart()
for (bom in boms) {
content = content.removePrefix(bom)
}
content = content.trimStart()
return content.trimEnd()
}
private fun processKeybox(xmlParser: XmlParser, index: Int): Pair<String, KeyBox> {
try {
val algorithmResult = xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index]")
val keyboxAlgorithm =
when (algorithmResult) {
is XmlParser.ParseResult.Success ->
algorithmResult.attributes["algorithm"]
?: throw IllegalArgumentException("Missing algorithm attribute")
is XmlParser.ParseResult.Error ->
throw Exception(algorithmResult.message, algorithmResult.cause)
}
val privateKeyResult =
xmlParser.obtainPath("AndroidAttestation.Keybox.Key[$index].PrivateKey")
val privateKeyContent =
when (privateKeyResult) {
is XmlParser.ParseResult.Success ->
privateKeyResult.attributes["text"]
?: throw IllegalArgumentException("Missing private key text")
is XmlParser.ParseResult.Error ->
throw Exception(privateKeyResult.message, privateKeyResult.cause)
}
val numberOfCertificatesResult =
xmlParser.obtainPath(
"AndroidAttestation.Keybox.Key[$index].CertificateChain.NumberOfCertificates"
)
val numberOfCertificates =
when (numberOfCertificatesResult) {
is XmlParser.ParseResult.Success ->
numberOfCertificatesResult.attributes["text"]?.toIntOrNull()
?: throw IllegalArgumentException("Invalid number of certificates")
is XmlParser.ParseResult.Error ->
throw Exception(
numberOfCertificatesResult.message,
numberOfCertificatesResult.cause,
)
}
val certificateChain = mutableListOf<Certificate>()
repeat(numberOfCertificates) { j ->
val certResult =
xmlParser.obtainPath(
"AndroidAttestation.Keybox.Key[$index].CertificateChain.Certificate[$j]"
)
val certContent =
when (certResult) {
is XmlParser.ParseResult.Success ->
certResult.attributes["text"]
?: throw IllegalArgumentException("Missing certificate text")
is XmlParser.ParseResult.Error ->
throw Exception(certResult.message, certResult.cause)
}
when (val certParseResult = CertificateUtils.parseCertificate(certContent)) {
is CertificateUtils.ParseResult.Success ->
certificateChain.add(certParseResult.data)
is CertificateUtils.ParseResult.Error ->
throw Exception(certParseResult.message, certParseResult.cause)
}
}
val pemKeyPair =
when (val keyParseResult = CertificateUtils.parseKeyPair(privateKeyContent)) {
is CertificateUtils.ParseResult.Success -> keyParseResult.data
is CertificateUtils.ParseResult.Error ->
throw Exception(keyParseResult.message, keyParseResult.cause)
}
val keyPair = CertificateUtils.convertPemToKeyPair(pemKeyPair)
val algorithmName =
when (keyboxAlgorithm.lowercase()) {
"ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
"rsa" -> KeyProperties.KEY_ALGORITHM_RSA
else -> keyboxAlgorithm
}
return algorithmName to KeyBox(pemKeyPair, keyPair, certificateChain)
} catch (t: Throwable) {
Logger.e("Error processing keybox $index", t)
throw t
}
}
}
@@ -0,0 +1,298 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS.config
import android.content.pm.IPackageManager
import android.os.Build
import android.os.FileObserver
import android.os.IBinder
import android.os.IInterface
import android.os.ServiceManager
import io.github.beakthoven.TrickyStoreOSS.AttestUtils.TEEStatus
import io.github.beakthoven.TrickyStoreOSS.KeyBoxUtils
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.io.File
object PkgConfig {
private val hackPackages = mutableSetOf<String>()
private val generatePackages = mutableSetOf<String>()
private val packageModes = mutableMapOf<String, Mode>()
private val packageKeyboxes = mutableMapOf<String, String>()
private val keyboxRegex = Regex("^\\[([a-zA-Z0-9_.-]+\\.xml)]$")
private const val DEFAULT_KEYBOX_FILE = "keybox.xml"
fun getKeyboxFileForUid(callingUid: Int): String =
runCatching {
val ps = getPm()?.getPackagesForUid(callingUid) ?: return DEFAULT_KEYBOX_FILE
for (pkg in ps) {
packageKeyboxes[pkg]?.let {
return it
}
}
return DEFAULT_KEYBOX_FILE
}
.getOrDefault(DEFAULT_KEYBOX_FILE)
enum class Mode {
AUTO,
LEAF_HACK,
GENERATE,
}
private fun updateTargetPackages(f: File?) =
runCatching {
hackPackages.clear()
generatePackages.clear()
packageModes.clear()
packageKeyboxes.clear()
var currentKeyboxFile = DEFAULT_KEYBOX_FILE
f?.readLines()?.forEach { line ->
val n = line.trim()
if (n.isBlank() || n.startsWith("#")) {
return@forEach // Skip comments and empty lines
}
val matchResult = keyboxRegex.find(n)
if (matchResult != null) {
currentKeyboxFile = matchResult.groupValues[1]
Logger.i(
"Switched to keybox file: $currentKeyboxFile for subsequent packages"
)
return@forEach
}
when {
n.endsWith("!") -> {
val pkg = n.removeSuffix("!").trim()
generatePackages.add(pkg)
packageModes[pkg] = Mode.GENERATE
packageKeyboxes[pkg] = currentKeyboxFile
}
n.endsWith("?") -> {
val pkg = n.removeSuffix("?").trim()
hackPackages.add(pkg)
packageModes[pkg] = Mode.LEAF_HACK
packageKeyboxes[pkg] = currentKeyboxFile
}
else -> {
// Auto mode
packageModes[n] = Mode.AUTO
packageKeyboxes[n] = currentKeyboxFile
}
}
}
Logger.i(
"update hack packages: $hackPackages, generate packages=$generatePackages, packageModes=$packageModes, , packageKeyboxes=$packageKeyboxes"
)
}
.onFailure { Logger.e("failed to update target files", it) }
// This function is now deprecated in favor of a more dynamic approach, but kept for simplicity.
// The key logic is now in KeyBoxUtils which will be called from the interceptors.
private fun updateKeyBox(f: File?) =
runCatching { KeyBoxUtils.readFromXml(f?.readText()) }
.onFailure { Logger.e("failed to update keybox", it) }
const val CONFIG_PATH = "/data/adb/tricky_store"
private const val TARGET_FILE = "target.txt"
private const val TEE_STATUS_FILE = "tee_status"
private const val PATCHLEVEL_FILE = "security_patch.txt"
private val root = File(CONFIG_PATH)
@Volatile private var teeBroken: Boolean? = null
private fun storeTEEStatus(root: File) {
val statusFile = File(root, TEE_STATUS_FILE)
teeBroken = !TEEStatus
try {
statusFile.writeText("teeBroken=${teeBroken}")
Logger.i("TEE status written to $statusFile: teeBroken=$teeBroken")
} catch (e: Exception) {
Logger.e("Failed to write TEE status: ${e.message}")
}
}
private fun loadTEEStatus(root: File) {
val statusFile = File(root, TEE_STATUS_FILE)
if (statusFile.exists()) {
val line = statusFile.readText().trim()
teeBroken = line == "teeBroken=true"
} else {
teeBroken = null
}
}
object ConfigObserver : FileObserver(root, CLOSE_WRITE or DELETE or MOVED_FROM or MOVED_TO) {
override fun onEvent(event: Int, path: String?) {
path ?: return
val f =
when (event) {
CLOSE_WRITE,
MOVED_TO -> File(root, path)
DELETE,
MOVED_FROM -> null
else -> return
}
when {
path == TARGET_FILE -> updateTargetPackages(f)
path.endsWith(".xml") -> {
// This is a simplification. A more robust solution would be to reload the
// specific keybox if it's in use.
// For now, we assume any XML change might affect the active keyboxes, prompting
// a reload where needed.
// The main logic for loading is now handled dynamically in KeyBoxUtils.
Logger.i("Keybox file $path changed. It will be re-read on next use.")
}
path == PATCHLEVEL_FILE -> updatePatchLevel(f)
}
}
}
fun initialize() {
root.mkdirs()
val scope = File(root, TARGET_FILE)
if (scope.exists()) {
updateTargetPackages(scope)
} else {
Logger.e("target.txt file not found, please put it to $scope !")
}
val keybox = File(root, DEFAULT_KEYBOX_FILE)
if (!keybox.exists()) {
Logger.e("default keybox file not found, please put it to $keybox !")
} else {
updateKeyBox(keybox)
}
storeTEEStatus(root)
val patchFile = File(root, PATCHLEVEL_FILE)
updatePatchLevel(if (patchFile.exists()) patchFile else null)
ConfigObserver.startWatching()
}
private var iPm: IPackageManager? = null
private val packageManagerDeathRecipient =
object : IBinder.DeathRecipient {
override fun binderDied() {
(iPm as? IInterface)?.asBinder()?.unlinkToDeath(this, 0)
iPm = null
}
}
fun getPm(): IPackageManager? {
if (iPm == null) {
val binder = waitAndGetSystemService("package") ?: return null
binder.linkToDeath(packageManagerDeathRecipient, 0)
iPm = IPackageManager.Stub.asInterface(binder)
}
return iPm
}
fun needHack(callingUid: Int): Boolean =
kotlin
.runCatching {
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
if (teeBroken == null) loadTEEStatus(root)
for (pkg in ps) {
when (packageModes[pkg]) {
Mode.LEAF_HACK -> return true
Mode.AUTO -> {
if (teeBroken == false) return true
}
else -> {}
}
}
return false
}
.onFailure { Logger.e("failed to get packages", it) }
.getOrNull() ?: false
fun needGenerate(callingUid: Int): Boolean =
kotlin
.runCatching {
val ps = getPm()?.getPackagesForUid(callingUid) ?: return false
if (teeBroken == null) loadTEEStatus(root)
for (pkg in ps) {
when (packageModes[pkg]) {
Mode.GENERATE -> return true
Mode.AUTO -> {
if (teeBroken == true) return true
}
else -> {}
}
}
return false
}
.onFailure { Logger.e("failed to get packages", it) }
.getOrNull() ?: false
@Volatile var _customPatchLevel: CustomPatchLevel? = null
fun updatePatchLevel(f: File?) =
runCatching {
if (f == null || !f.exists()) {
_customPatchLevel = null
return@runCatching
}
val lines =
f.readLines()
.map { it.trim() }
.filter { it.isNotEmpty() && !it.startsWith("#") }
if (lines.isEmpty()) {
_customPatchLevel = null
return@runCatching
}
if (lines.size == 1 && !lines[0].contains("=")) {
_customPatchLevel = CustomPatchLevel(all = lines[0])
return@runCatching
}
val map = mutableMapOf<String, String>()
for (line in lines) {
val idx = line.indexOf('=')
if (idx > 0) {
val key = line.substring(0, idx).trim().lowercase()
val value = line.substring(idx + 1).trim()
map[key] = value
}
}
val all = map["all"]
_customPatchLevel =
CustomPatchLevel(
system = map["system"] ?: all,
vendor = map["vendor"] ?: all,
boot = map["boot"] ?: all,
all = all,
)
}
.onFailure { Logger.e("failed to update patch level", it) }
private fun waitAndGetSystemService(name: String): IBinder? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return ServiceManager.waitForService(name)
}
var tryCount = 0
while (tryCount++ < 70) {
val service = ServiceManager.getService(name)
if (service != null) {
Logger.d("Got $name service after $tryCount tries")
return service
}
Thread.sleep(500)
}
Logger.e("Failed to get $name service")
return null
}
}
data class CustomPatchLevel(
val system: String? = null,
val vendor: String? = null,
val boot: String? = null,
val all: String? = null,
)
@@ -0,0 +1,199 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.os.Binder
import android.os.IBinder
import android.os.Parcel
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
open class BinderInterceptor : Binder() {
sealed class Result
data object Skip : Result()
data object Continue : Result()
data class OverrideData(val data: Parcel) : Result()
data class OverrideReply(val code: Int = 0, val reply: Parcel) : Result()
companion object {
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
private const val REGISTER_INTERCEPTOR_CODE = 1
private const val PRE_TRANSACT_CODE = 1
private const val POST_TRANSACT_CODE = 2
private const val RESULT_SKIP = 1
private const val RESULT_CONTINUE = 2
private const val RESULT_OVERRIDE_REPLY = 3
private const val RESULT_OVERRIDE_DATA = 4
fun getBinderBackdoor(binder: IBinder): IBinder? {
val data = Parcel.obtain()
val reply = Parcel.obtain()
return try {
val success = binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)
if (success) {
Logger.d("Backdoor access granted for binder: $binder")
reply.readStrongBinder()
} else {
Logger.d("Backdoor access denied for binder: $binder")
null
}
} catch (e: Exception) {
Logger.e("Failed to access binder backdoor", e)
null
} finally {
data.recycle()
reply.recycle()
}
}
fun registerBinderInterceptor(
backdoor: IBinder,
target: IBinder,
interceptor: BinderInterceptor,
) {
val data = Parcel.obtain()
val reply = Parcel.obtain()
try {
data.writeStrongBinder(target)
data.writeStrongBinder(interceptor)
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
Logger.d("Registered interceptor for target: $target")
} catch (e: Exception) {
Logger.e("Failed to register binder interceptor", e)
} finally {
data.recycle()
reply.recycle()
}
}
}
open fun onPreTransact(
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): Result = Skip
open fun onPostTransact(
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): Result = Skip
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
val result =
when (code) {
PRE_TRANSACT_CODE -> handlePreTransact(data)
POST_TRANSACT_CODE -> handlePostTransact(data)
else -> return super.onTransact(code, data, reply, flags)
}
writeResultToReply(result, reply!!)
return true
}
private fun handlePreTransact(data: Parcel): Result {
val target = data.readStrongBinder()
val transactionCode = data.readInt()
val transactionFlags = data.readInt()
val callingUid = data.readInt()
val callingPid = data.readInt()
val dataSize = data.readLong()
val transactionData = Parcel.obtain()
return try {
transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
transactionData.setDataPosition(0)
onPreTransact(
target,
transactionCode,
transactionFlags,
callingUid,
callingPid,
transactionData,
)
} finally {
transactionData.recycle()
}
}
private fun handlePostTransact(data: Parcel): Result {
val target = data.readStrongBinder()
val transactionCode = data.readInt()
val transactionFlags = data.readInt()
val callingUid = data.readInt()
val callingPid = data.readInt()
val resultCode = data.readInt()
val transactionData = Parcel.obtain()
val transactionReply = Parcel.obtain()
return try {
val dataSize = data.readLong().toInt()
transactionData.appendFrom(data, data.dataPosition(), dataSize)
transactionData.setDataPosition(0)
data.setDataPosition(data.dataPosition() + dataSize)
val replySize = data.readLong().toInt()
val reply =
if (replySize > 0) {
transactionReply.appendFrom(data, data.dataPosition(), replySize)
transactionReply.setDataPosition(0)
transactionReply
} else null
onPostTransact(
target,
transactionCode,
transactionFlags,
callingUid,
callingPid,
transactionData,
reply,
resultCode,
)
} finally {
transactionData.recycle()
transactionReply.recycle()
}
}
private fun writeResultToReply(result: Result, reply: Parcel) {
when (result) {
Skip -> reply.writeInt(RESULT_SKIP)
Continue -> reply.writeInt(RESULT_CONTINUE)
is OverrideReply -> {
reply.writeInt(RESULT_OVERRIDE_REPLY)
reply.writeInt(result.code)
reply.writeLong(result.reply.dataSize().toLong())
reply.appendFrom(result.reply, 0, result.reply.dataSize())
result.reply.recycle()
}
is OverrideData -> {
reply.writeInt(RESULT_OVERRIDE_DATA)
reply.writeLong(result.data.dataSize().toLong())
reply.appendFrom(result.data, 0, result.data.dataSize())
result.data.recycle()
}
}
}
}
@@ -0,0 +1,153 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.os.IBinder
import android.os.Parcel
import android.os.Parcelable
import android.os.ServiceManager
import android.security.KeyStore
import android.security.keystore.KeystoreResponse
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import kotlin.system.exitProcess
abstract class BaseKeystoreInterceptor : BinderInterceptor() {
protected lateinit var keystore: IBinder
protected var triedCount = 0
protected var injected = false
protected open val maxRetries: Int = 3
protected abstract val serviceName: String
protected abstract val injectionCommand: String
protected abstract val processName: String
fun tryRunKeystoreInterceptor(): Boolean {
Logger.i("Trying to register ${this::class.simpleName} (attempt $triedCount)...")
val service = getService() ?: return false
val backdoor = getBinderBackdoor(service)
return if (backdoor != null) {
setupInterceptor(service, backdoor)
} else {
handleMissingBackdoor()
}
}
protected open fun getService(): IBinder? = ServiceManager.getService(serviceName)
protected open fun setupInterceptor(service: IBinder, backdoor: IBinder): Boolean {
keystore = service
Logger.i("Registering for $serviceName: $keystore")
registerBinderInterceptor(backdoor, service, this)
service.linkToDeath(createDeathRecipient(), 0)
onInterceptorSetup(service, backdoor)
return true
}
private fun handleMissingBackdoor(): Boolean {
if (triedCount >= maxRetries) {
Logger.e("Tried injection $maxRetries times but still no backdoor, exiting")
exitProcess(1)
}
if (!injected) {
performInjection()
injected = true
}
triedCount++
return false
}
protected open fun performInjection() {
Logger.i("Attempting to inject into $processName...")
val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
Logger.d("Injection command: ${command.joinToString(" ")}")
val process = Runtime.getRuntime().exec(command)
if (process.waitFor() != 0) {
Logger.e("Injection failed! Daemon will exit")
exitProcess(1)
}
Logger.i("Injection completed successfully")
}
protected open fun createDeathRecipient(): IBinder.DeathRecipient =
object : IBinder.DeathRecipient {
override fun binderDied() {
Logger.d("$serviceName died, daemon restarting")
exitProcess(0)
}
}
protected open fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
// Default implementation does nothing
}
}
object InterceptorUtils {
fun getTransactCode(clazz: Class<*>, method: String): Int =
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }.getInt(null)
fun createSuccessKeystoreResponse(): KeystoreResponse {
val parcel = Parcel.obtain()
try {
parcel.writeInt(KeyStore.NO_ERROR)
parcel.writeString("")
parcel.setDataPosition(0)
return KeystoreResponse.CREATOR.createFromParcel(parcel)
} finally {
parcel.recycle()
}
}
fun createSuccessReply(resultCode: Int = KeyStore.NO_ERROR): BinderInterceptor.OverrideReply {
val parcel = Parcel.obtain()
parcel.writeNoException()
parcel.writeInt(resultCode)
return BinderInterceptor.OverrideReply(0, parcel)
}
fun createByteArrayReply(
data: ByteArray,
resultCode: Int = KeyStore.NO_ERROR,
): BinderInterceptor.OverrideReply {
val parcel = Parcel.obtain()
parcel.writeNoException()
parcel.writeByteArray(data)
return BinderInterceptor.OverrideReply(resultCode, parcel)
}
fun <T : Parcelable?> createTypedObjectReply(
obj: T,
flags: Int = 0,
resultCode: Int = 0,
): BinderInterceptor.OverrideReply {
val parcel = Parcel.obtain()
parcel.writeNoException()
parcel.writeTypedObject(obj, flags)
return BinderInterceptor.OverrideReply(resultCode, parcel)
}
fun String.extractAlias(): String {
return when {
contains("_") -> split("_")[1]
else -> this
}
}
fun Parcel.hasException(): Boolean {
return kotlin.runCatching { readException() }.exceptionOrNull() != null
}
}
@@ -0,0 +1,198 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.annotation.SuppressLint
import android.hardware.security.keymint.SecurityLevel
import android.os.IBinder
import android.os.Parcel
import android.system.keystore2.IKeystoreService
import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse
import io.github.beakthoven.TrickyStoreOSS.CertificateHack
import io.github.beakthoven.TrickyStoreOSS.CertificateUtils
import io.github.beakthoven.TrickyStoreOSS.KeyBoxUtils
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createTypedObjectReply
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
@SuppressLint("BlockedPrivateApi")
object Keystore2Interceptor : BaseKeystoreInterceptor() {
private val getKeyEntryTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
private val deleteKeyTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
override val serviceName = "android.system.keystore2.IKeystoreService/default"
override val processName = "keystore2"
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
private var teeInterceptor: SecurityLevelInterceptor? = null
private var strongBoxInterceptor: SecurityLevelInterceptor? = null
override fun onInterceptorSetup(service: IBinder, backdoor: IBinder) {
setupSecurityLevelInterceptors(service, backdoor)
}
private fun setupSecurityLevelInterceptors(service: IBinder, backdoor: IBinder) {
val ks = IKeystoreService.Stub.asInterface(service)
val tee =
kotlin
.runCatching { ks.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT) }
.getOrNull()
if (tee != null) {
Logger.i("Registering for TEE SecurityLevel: $tee")
val interceptor = SecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
registerBinderInterceptor(backdoor, tee.asBinder(), interceptor)
teeInterceptor = interceptor
} else {
Logger.i("No TEE SecurityLevel found")
}
val strongBox =
kotlin.runCatching { ks.getSecurityLevel(SecurityLevel.STRONGBOX) }.getOrNull()
if (strongBox != null) {
Logger.i("Registering for StrongBox SecurityLevel: $strongBox")
val interceptor = SecurityLevelInterceptor(strongBox, SecurityLevel.STRONGBOX)
registerBinderInterceptor(backdoor, strongBox.asBinder(), interceptor)
strongBoxInterceptor = interceptor
} else {
Logger.i("No StrongBox SecurityLevel found")
}
}
override fun onPreTransact(
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): Result {
if (code == getKeyEntryTransaction) {
if (KeyBoxUtils.hasKeyboxes()) {
Logger.d(
"intercept pre $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()}"
)
try {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return Skip
if (PkgConfig.needGenerate(callingUid)) {
val response =
SecurityLevelInterceptor.getKeyResponse(callingUid, descriptor.alias)
if (response != null) {
Logger.i(
"Found generated response for uid=$callingUid alias=${descriptor.alias}"
)
return createTypedObjectReply(response)
} else {
Logger.e(
"No generated response found for uid=$callingUid alias=${descriptor.alias}"
)
val nullParcel = Parcel.obtain()
nullParcel.writeTypedObject(null as KeyEntryResponse?, 0)
return OverrideReply(0, nullParcel)
}
} else if (PkgConfig.needHack(callingUid)) {
if (
SecurityLevelInterceptor.shouldSkipLeafHack(
callingUid,
descriptor.alias,
)
) {
Logger.i("skip leaf hack for uid=$callingUid alias=${descriptor.alias}")
val response =
SecurityLevelInterceptor.getKeyResponse(
callingUid,
descriptor.alias,
)
if (response != null) {
Logger.i(
"Found generated response for uid=$callingUid alias=${descriptor.alias}"
)
return createTypedObjectReply(response)
} else {
Logger.e(
"No generated response found for uid=$callingUid alias=${descriptor.alias}"
)
val nullParcel = Parcel.obtain()
nullParcel.writeTypedObject(null as KeyEntryResponse?, 0)
return OverrideReply(0, nullParcel)
}
} else {
Logger.i(
"proceeding with leaf hack for uid=$callingUid alias=${descriptor.alias}"
)
return Continue
}
}
return Skip
} catch (e: Exception) {
Logger.e("Exception in onPreTransact uid=$callingUid pid=$callingPid!", e)
return Skip
}
}
}
return Skip
}
override fun onPostTransact(
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): Result {
if (target != keystore || reply == null) return Skip
if (reply.hasException()) return Skip
val p = Parcel.obtain()
Logger.d(
"intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}"
)
if (code == deleteKeyTransaction && resultCode == 0) {
data.enforceInterface("android.system.keystore2.IKeystoreService")
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
if (keyDescriptor == null || keyDescriptor.domain == 0) return Skip
SecurityLevelInterceptor.keys.remove(
SecurityLevelInterceptor.Key(callingUid, keyDescriptor.alias)
)
return Skip
} else if (code == getKeyEntryTransaction) {
try {
data.enforceInterface("android.system.keystore2.IKeystoreService")
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)
if (response != null) {
val chain = CertificateUtils.run { response.getCertificateChain() }
if (chain != null) {
val newChain = CertificateHack.hackCertificateChain(chain, callingUid)
response.putCertificateChain(newChain).getOrThrow()
Logger.i("Hacked certificate for uid=$callingUid")
return createTypedObjectReply(response)
} else {
p.recycle()
}
} else {
p.recycle()
}
} catch (t: Throwable) {
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
p.recycle()
}
}
return Skip
}
}
@@ -0,0 +1,296 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.annotation.SuppressLint
import android.os.IBinder
import android.os.Parcel
import android.security.Credentials
import android.security.KeyStore
import android.security.keymaster.ExportResult
import android.security.keymaster.KeyCharacteristics
import android.security.keymaster.KeymasterArguments
import android.security.keymaster.KeymasterCertificateChain
import android.security.keymaster.KeymasterDefs
import android.security.keystore.IKeystoreCertificateChainCallback
import android.security.keystore.IKeystoreExportKeyCallback
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
import android.security.keystore.IKeystoreService
import io.github.beakthoven.TrickyStoreOSS.CertificateGen
import io.github.beakthoven.TrickyStoreOSS.CertificateHack
import io.github.beakthoven.TrickyStoreOSS.KeyBoxUtils
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createByteArrayReply
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessKeystoreResponse
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.createSuccessReply
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.extractAlias
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.hasException
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import java.math.BigInteger
import java.security.KeyPair
import java.util.Date
@SuppressLint("BlockedPrivateApi")
object KeystoreInterceptor : BaseKeystoreInterceptor() {
private val getTransaction = getTransactCode(IKeystoreService.Stub::class.java, "get")
private val generateKeyTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
private val getKeyCharacteristicsTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
private val exportKeyTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
private val attestKeyTransaction =
getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
override val serviceName = "android.security.keystore"
override val processName = "keystore"
override val injectionCommand = "exec ./inject `pidof keystore` libTEESimulator.so entry"
private const val DESCRIPTOR = "android.security.keystore.IKeystoreService"
private val keyArguments = HashMap<Key, CertificateGen.KeyGenParameters>()
private val keyPairs = HashMap<Key, KeyPair>()
data class Key(val uid: Int, val alias: String)
override fun onPreTransact(
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): Result {
if (KeyBoxUtils.hasKeyboxes()) {
if (code == getTransaction) {
if (PkgConfig.needHack(callingUid)) {
return Continue
} else if (PkgConfig.needGenerate(callingUid)) {
return Skip
}
} else if (PkgConfig.needGenerate(callingUid)) {
when (code) {
generateKeyTransaction -> {
kotlin
.runCatching {
data.enforceInterface(DESCRIPTOR)
val callback =
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(
data.readStrongBinder()
)
val alias = data.readString()!!.extractAlias()
Logger.i("generateKeyTransaction uid $callingUid alias $alias")
val check = data.readInt()
val kma = KeymasterArguments()
val kgp = CertificateGen.KeyGenParameters()
if (check == 1) {
kma.readFromParcel(data)
kgp.algorithm = kma.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
kgp.keySize =
kma.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
// kgp.setEcCurveName(kgp.keySize)
kgp.purpose = kma.getEnums(KeymasterDefs.KM_TAG_PURPOSE)
kgp.digest = kma.getEnums(KeymasterDefs.KM_TAG_DIGEST)
kgp.certificateNotBefore =
kma.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date())
if (kgp.algorithm == KeymasterDefs.KM_ALGORITHM_RSA) {
try {
val getArgumentByTag =
KeymasterArguments::class
.java
.getDeclaredMethods()
.first { it.name == "getArgumentByTag" }
getArgumentByTag.isAccessible = true
val rsaArgument =
getArgumentByTag.invoke(
kma,
KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
)
val getLongTagValue =
KeymasterArguments::class
.java
.getDeclaredMethods()
.first { it.name == "getLongTagValue" }
getLongTagValue.isAccessible = true
kgp.rsaPublicExponent =
getLongTagValue.invoke(kma, rsaArgument)
as BigInteger
} catch (ex: Exception) {
Logger.e("Read rsaPublicExponent error", ex)
}
}
keyArguments[Key(callingUid, alias)] = kgp
}
val kc = KeyCharacteristics()
kc.swEnforced = KeymasterArguments()
kc.hwEnforced = kma
val ksr = createSuccessKeystoreResponse()
callback.onFinished(ksr, kc)
return createSuccessReply()
}
.onFailure { Logger.e("generateKeyTransaction error", it) }
}
getKeyCharacteristicsTransaction -> {
kotlin
.runCatching {
data.enforceInterface(DESCRIPTOR)
val callback =
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(
data.readStrongBinder()
)
val alias = data.readString()!!.extractAlias()
Logger.i(
"getKeyCharacteristicsTransaction uid $callingUid alias $alias"
)
val kc = KeyCharacteristics()
val kma = KeymasterArguments()
kma.addEnum(
KeymasterDefs.KM_TAG_ALGORITHM,
keyArguments[Key(callingUid, alias)]!!.algorithm,
)
kc.swEnforced = KeymasterArguments()
kc.hwEnforced = kma
val ksr = createSuccessKeystoreResponse()
callback.onFinished(ksr, kc)
return createSuccessReply()
}
.onFailure { Logger.e("getKeyCharacteristicsTransaction error", it) }
}
exportKeyTransaction -> {
kotlin
.runCatching {
data.enforceInterface(DESCRIPTOR)
val callback =
IKeystoreExportKeyCallback.Stub.asInterface(
data.readStrongBinder()
)
val alias = data.readString()!!.extractAlias()
Logger.i("exportKeyTransaction uid $callingUid alias $alias")
val kp =
CertificateGen.generateKeyPair(
keyArguments[Key(callingUid, alias)]!!
)
keyPairs[Key(callingUid, alias)] = kp!!
val erP = Parcel.obtain()
erP.writeInt(KeyStore.NO_ERROR)
erP.writeByteArray(kp.public.encoded)
erP.setDataPosition(0)
val er = ExportResult.CREATOR.createFromParcel(erP)
erP.recycle()
callback.onFinished(er)
return createSuccessReply()
}
.onFailure { Logger.e("exportKeyTransaction error", it) }
}
attestKeyTransaction -> {
kotlin
.runCatching {
data.enforceInterface(DESCRIPTOR)
val callback =
IKeystoreCertificateChainCallback.Stub.asInterface(
data.readStrongBinder()
)
val alias = data.readString()!!.extractAlias()
Logger.i("attestKeyTransaction uid $callingUid alias $alias")
val check = data.readInt()
val kma = KeymasterArguments()
if (check == 1) {
kma.readFromParcel(data)
val attestationChallenge =
kma.getBytes(
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE,
ByteArray(0),
)
val ksr = createSuccessKeystoreResponse()
val key = Key(callingUid, alias)
val ka = keyArguments[key]!!
ka.attestationChallenge = attestationChallenge
val chain =
CertificateGen.generateChain(
callingUid,
ka,
keyPairs[key]!!,
)
val kcc = KeymasterCertificateChain(chain)
callback.onFinished(ksr, kcc)
}
return createSuccessReply()
}
.onFailure { Logger.e("attestKeyTransaction error", it) }
}
}
}
}
return Skip
}
override fun onPostTransact(
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): Result {
if (target != keystore || code != getTransaction || reply == null) return Skip
if (reply.hasException()) return Skip
val p = Parcel.obtain()
Logger.d(
"intercept post $target uid=$callingUid pid=$callingPid dataSz=${data.dataSize()} replySz=${reply.dataSize()}"
)
try {
data.enforceInterface(DESCRIPTOR)
val alias = data.readString() ?: ""
var response = reply.createByteArray()
when {
alias.startsWith(Credentials.USER_CERTIFICATE) -> {
response =
CertificateHack.hackUserCertificate(
response!!,
alias.extractAlias(),
callingUid,
)
Logger.i("Hacked leaf certificate for uid=$callingUid")
return createByteArrayReply(response)
}
alias.startsWith(Credentials.CA_CERTIFICATE) -> {
response =
CertificateHack.hackCACertificateChain(
response!!,
alias.extractAlias(),
callingUid,
)
Logger.i("Hacked CA certificate chain for uid=$callingUid")
return createByteArrayReply(response)
}
else -> p.recycle()
}
} catch (t: Throwable) {
Logger.e("failed to hack certificate chain of uid=$callingUid pid=$callingPid!", t)
p.recycle()
}
return Skip
}
}
@@ -0,0 +1,207 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS.interceptors
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyParameterValue
import android.hardware.security.keymint.Tag
import android.os.IBinder
import android.os.Parcel
import android.system.keystore2.Authorization
import android.system.keystore2.IKeystoreSecurityLevel
import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse
import android.system.keystore2.KeyMetadata
import androidx.annotation.Keep
import io.github.beakthoven.TrickyStoreOSS.CertificateGen
import io.github.beakthoven.TrickyStoreOSS.config.PkgConfig
import io.github.beakthoven.TrickyStoreOSS.interceptors.InterceptorUtils.getTransactCode
import io.github.beakthoven.TrickyStoreOSS.logging.Logger
import io.github.beakthoven.TrickyStoreOSS.putCertificateChain
import java.security.KeyPair
import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap
class SecurityLevelInterceptor(
private val original: IKeystoreSecurityLevel,
private val level: Int,
) : BinderInterceptor() {
companion object {
private val generateKeyTransaction =
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
private val deleteKeyTransaction =
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "deleteKey")
private val createOperationTransaction =
getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "createOperation")
@Keep val keys = ConcurrentHashMap<Key, Info>()
@Keep val keyPairs = ConcurrentHashMap<Key, Pair<KeyPair, List<Certificate>>>()
@Keep val skipLeafHacks = ConcurrentHashMap<Key, Boolean>()
@Keep
fun getKeyResponse(uid: Int, alias: String): KeyEntryResponse? =
keys[Key(uid, alias)]?.response
@Keep
fun getKeyPairs(uid: Int, alias: String): Pair<KeyPair, List<Certificate>>? =
keyPairs[Key(uid, alias)]
@Keep
fun shouldSkipLeafHack(uid: Int, alias: String): Boolean =
skipLeafHacks[Key(uid, alias)] ?: false
}
data class Key(val uid: Int, val alias: String)
data class Info(val keyPair: KeyPair, val response: KeyEntryResponse)
override fun onPreTransact(
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): Result {
if (code == generateKeyTransaction) {
Logger.i("intercept key gen uid=$callingUid pid=$callingPid")
kotlin
.runCatching {
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR) ?: return@runCatching
val attestationKeyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
val params = data.createTypedArray(KeyParameter.CREATOR)!!
val aFlags = data.readInt()
val entropy = data.createByteArray()
val kgp = CertificateGen.KeyGenParameters(params)
if (PkgConfig.needGenerate(callingUid)) {
val pair =
CertificateGen.generateKeyPair(
callingUid,
keyDescriptor,
attestationKeyDescriptor,
kgp,
level,
) ?: return@runCatching
keyPairs[Key(callingUid, keyDescriptor.alias)] =
Pair(pair.first, pair.second)
val response =
buildResponse(
pair.second,
kgp,
attestationKeyDescriptor ?: keyDescriptor,
)
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
val p = Parcel.obtain()
p.writeNoException()
p.writeTypedObject(response.metadata, 0)
return OverrideReply(0, p)
} else if (PkgConfig.needHack(callingUid)) {
if ((kgp.purpose.contains(7)) || (attestationKeyDescriptor != null)) {
Logger.i(
"Generating key in generation mode for attestation: uid=$callingUid alias=${keyDescriptor.alias}"
)
val pair =
CertificateGen.generateKeyPair(
callingUid,
keyDescriptor,
attestationKeyDescriptor,
kgp,
level,
) ?: return@runCatching
keyPairs[Key(callingUid, keyDescriptor.alias)] =
Pair(pair.first, pair.second)
val response =
buildResponse(
pair.second,
kgp,
attestationKeyDescriptor ?: keyDescriptor,
)
keys[Key(callingUid, keyDescriptor.alias)] = Info(pair.first, response)
SecurityLevelInterceptor.skipLeafHacks[
Key(callingUid, keyDescriptor.alias)] = true
val p = Parcel.obtain()
p.writeNoException()
p.writeTypedObject(response.metadata, 0)
return OverrideReply(0, p)
} else {
skipLeafHacks.remove(Key(callingUid, keyDescriptor.alias))
Logger.i(
"Cleared skip flag for non-attestation key: uid=$callingUid alias=${keyDescriptor.alias}"
)
return Skip
}
}
}
.onFailure { Logger.e("parse key gen request", it) }
}
return Skip
}
private fun buildResponse(
chain: List<Certificate>,
params: CertificateGen.KeyGenParameters,
descriptor: KeyDescriptor,
): KeyEntryResponse {
val response = KeyEntryResponse()
val metadata = KeyMetadata()
metadata.keySecurityLevel = level
metadata.putCertificateChain(chain.toTypedArray()).getOrThrow()
val d = KeyDescriptor()
d.domain = descriptor.domain
d.nspace = descriptor.nspace
metadata.key = d
val authorizations = ArrayList<Authorization>()
var a: Authorization
for (i in params.purpose.toList()) {
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.PURPOSE
a.keyParameter.value = KeyParameterValue.keyPurpose(i)
a.securityLevel = level
authorizations.add(a)
}
for (i in params.digest.toList()) {
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.DIGEST
a.keyParameter.value = KeyParameterValue.digest(i)
a.securityLevel = level
authorizations.add(a)
}
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.ALGORITHM
a.keyParameter.value = KeyParameterValue.algorithm(params.algorithm)
a.securityLevel = level
authorizations.add(a)
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.KEY_SIZE
a.keyParameter.value = KeyParameterValue.integer(params.keySize)
a.securityLevel = level
authorizations.add(a)
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.EC_CURVE
a.keyParameter.value = KeyParameterValue.ecCurve(params.ecCurve)
a.securityLevel = level
authorizations.add(a)
a = Authorization()
a.keyParameter = KeyParameter()
a.keyParameter.tag = Tag.NO_AUTH_REQUIRED
a.keyParameter.value = KeyParameterValue.boolValue(true)
a.securityLevel = level
authorizations.add(a)
metadata.authorizations = authorizations.toTypedArray<Authorization>()
response.metadata = metadata
response.iSecurityLevel = original
return response
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package io.github.beakthoven.TrickyStoreOSS.logging
import android.util.Log
object Logger {
const val TAG = "TTESimulator"
sealed class LogLevel(val priority: Int) {
object Debug : LogLevel(Log.DEBUG)
object Info : LogLevel(Log.INFO)
object Warning : LogLevel(Log.WARN)
object Error : LogLevel(Log.ERROR)
object Verbose : LogLevel(Log.VERBOSE)
}
fun d(message: String) {
Log.d(TAG, message)
}
fun e(message: String) {
Log.e(TAG, message)
}
fun e(message: String, throwable: Throwable) {
Log.e(TAG, "fatal: $message", throwable)
}
fun i(message: String) {
Log.i(TAG, message)
}
fun w(message: String) {
Log.w(TAG, message)
}
fun w(message: String, throwable: Throwable) {
Log.w(TAG, message, throwable)
}
fun v(message: String) {
Log.v(TAG, message)
}
fun log(level: LogLevel, message: String, throwable: Throwable? = null) {
when (level) {
is LogLevel.Debug ->
if (throwable != null) Log.d(TAG, message, throwable) else Log.d(TAG, message)
is LogLevel.Info ->
if (throwable != null) Log.i(TAG, message, throwable) else Log.i(TAG, message)
is LogLevel.Warning ->
if (throwable != null) Log.w(TAG, message, throwable) else Log.w(TAG, message)
is LogLevel.Error ->
if (throwable != null) Log.e(TAG, message, throwable) else Log.e(TAG, message)
is LogLevel.Verbose ->
if (throwable != null) Log.v(TAG, message, throwable) else Log.v(TAG, message)
}
}
fun logIf(level: LogLevel, condition: Boolean = true, messageProvider: () -> String) {
if (condition && Log.isLoggable(TAG, level.priority)) {
log(level, messageProvider())
}
}
}
@@ -1,132 +0,0 @@
package org.matrix.TEESimulator
import android.app.ActivityThread
import android.app.Application
import android.content.Context
import android.content.ContextWrapper
import android.os.Build
import android.os.Looper
import java.security.Security
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.NativeCertGen
import org.matrix.TEESimulator.util.AndroidDeviceUtils
/**
* Main application object for TEESimulator. This object manages the application's lifecycle,
* including initialization of interceptors and maintaining the service's primary execution loop.
*/
object App {
// The delay in milliseconds before retrying to initialize the interceptor.
private const val RETRY_DELAY_MS = 1000L
/**
* The main entry point of the TEESimulator application.
*
* @param args Command line arguments (not used).
*/
@JvmStatic
fun main(args: Array<String>) {
SystemLogger.info("Welcome to TEESimulator!")
try {
// Initialize the Android framework environment
prepareEnvironment()
// Initialize and start the appropriate keystore interceptors.
initializeInterceptors()
// Load the package configuration.
ConfigurationManager.initialize()
// Set up the device's boot key and hash, which are crucial for attestation.
AndroidDeviceUtils.setupBootKeyAndHash()
// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
// We must remove the system provider first to ensure the full Bouncy Castle library
// (packaged with the app) is used.
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider())
NativeCertGen.initialize("/data/adb/modules/tricky_store/libcertgen.so")
// This starts the message queue processing. It blocks here indefinitely
// processing messages until Looper.myLooper().quit() is called.
Looper.loop()
} catch (e: Exception) {
SystemLogger.error("A fatal error occurred in the main application thread.", e)
throw e
}
}
/** Initializes the necessary Android framework internals to satisfy KeyStore requirements. */
private fun prepareEnvironment() {
// 1. Prepare Main Looper
if (Looper.getMainLooper() == null) {
@Suppress("deprecation") Looper.prepareMainLooper()
}
// 2. Initialize ActivityThread for the current process
val activityThread = ActivityThread.systemMain()
// 3. Get the system context
val systemContext = activityThread.getSystemContext()
// 4. Create a dummy Application object and attach the context
val app = Application()
val attachMethod =
ContextWrapper::class.java.getDeclaredMethod("attachBaseContext", Context::class.java)
attachMethod.isAccessible = true
attachMethod.invoke(app, systemContext)
// 5. Inject this application object into ActivityThread's mInitialApplication field.
// This is what KeyStore.getApplicationContext() looks for.
val mInitialApplicationField =
ActivityThread::class.java.getDeclaredField("mInitialApplication")
mInitialApplicationField.isAccessible = true
mInitialApplicationField.set(activityThread, app)
}
/**
* Selects and initializes the correct keystore interceptor based on the Android SDK version. It
* retries initialization until it succeeds.
*/
private fun initializeInterceptors() {
val interceptor = selectKeystoreInterceptor()
// Continuously try to run the interceptor until it's successfully initialized.
while (!interceptor.tryRunKeystoreInterceptor()) {
SystemLogger.debug("Retrying interceptor initialization...")
Thread.sleep(RETRY_DELAY_MS)
}
SystemLogger.info("Interceptors initialized successfully.")
}
/**
* Determines which keystore interceptor to use based on the device's Android version.
*
* @return The appropriate keystore interceptor instance.
*/
private fun selectKeystoreInterceptor(): AbstractKeystoreInterceptor =
when {
// For Android Q (10) and R (11), use the original KeystoreInterceptor.
Build.VERSION.SDK_INT in Build.VERSION_CODES.Q..Build.VERSION_CODES.R -> {
SystemLogger.info(
"Using KeystoreInterceptor for Android Q/R (SDK ${Build.VERSION.SDK_INT})"
)
android.security.keystore.AndroidKeyStoreProvider.install()
KeystoreInterceptor
}
// For Android S (12) and newer, use the Keystore2Interceptor.
else -> {
SystemLogger.info(
"Using Keystore2Interceptor for Android S and later (SDK ${Build.VERSION.SDK_INT})"
)
android.security.keystore2.AndroidKeyStoreProvider.install()
Keystore2Interceptor
}
}
}
@@ -1,404 +0,0 @@
package org.matrix.TEESimulator.attestation
import android.content.pm.PackageManager
import android.os.Build
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1Enumerated
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.DERSet
import org.bouncycastle.asn1.DERTaggedObject
import org.bouncycastle.asn1.x509.Extension
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.util.AndroidDeviceUtils
import org.matrix.TEESimulator.util.AndroidDeviceUtils.DO_NOT_REPORT
/**
* A builder object responsible for constructing the ASN.1 DER-encoded Android Key Attestation
* extension.
*/
object AttestationBuilder {
/**
* Builds the complete X.509 attestation extension.
*
* @param params The parsed key generation parameters.
* @param uid The UID of the application requesting attestation.
* @param securityLevel The security level (e.g., TEE, StrongBox) to report.
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
*/
fun buildAttestationExtension(
params: KeyMintAttestation,
uid: Int,
securityLevel: Int,
): Extension {
val keyDescription = buildKeyDescription(params, uid, securityLevel)
var formattedString =
keyDescription.joinToString(separator = ", ") {
AttestationPatcher.formatAsn1Primitive(it)
}
SystemLogger.verbose("Forged attestation data: ${formattedString}")
return Extension(ATTESTATION_OID, false, DEROctetString(keyDescription.encoded))
}
/**
* Builds the `RootOfTrust` ASN.1 sequence. This contains critical boot state information.
*
* @param originalRootOfTrust An optional, pre-existing RoT to extract the boot hash from.
* @return The constructed [DERSequence] for the Root of Trust.
*/
internal fun buildRootOfTrust(originalRootOfTrust: ASN1Encodable?): DERSequence {
val rootOfTrustElements = arrayOfNulls<ASN1Encodable>(4)
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] =
DEROctetString(AndroidDeviceUtils.bootKey)
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] =
ASN1Boolean.TRUE // deviceLocked: true, for security
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] =
ASN1Enumerated(0) // verifiedBootState: Verified
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] =
DEROctetString(AndroidDeviceUtils.bootHash)
return DERSequence(rootOfTrustElements)
}
/**
* Assembles a map representing the desired state of simulated hardware-enforced properties. A
* null value for a given tag indicates that it should be removed from the attestation.
*
* @param uid The UID of the calling application.
* @return A map where keys are attestation tag numbers and values are the desired
* [DERTaggedObject] or null to signify removal.
*/
fun getSimulatedHardwareProperties(uid: Int): Map<Int, DERTaggedObject?> {
val properties = mutableMapOf<Int, DERTaggedObject?>()
// OS Version is always present.
properties[AttestationConstants.TAG_OS_VERSION] =
DERTaggedObject(
true,
AttestationConstants.TAG_OS_VERSION,
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
)
val osPatch = AndroidDeviceUtils.getPatchLevel(uid)
properties[AttestationConstants.TAG_OS_PATCHLEVEL] =
if (osPatch != DO_NOT_REPORT) {
DERTaggedObject(
true,
AttestationConstants.TAG_OS_PATCHLEVEL,
ASN1Integer(osPatch.toLong()),
)
} else {
null // Signal for removal
}
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(uid)
properties[AttestationConstants.TAG_VENDOR_PATCHLEVEL] =
if (vendorPatch != DO_NOT_REPORT) {
DERTaggedObject(
true,
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
ASN1Integer(vendorPatch.toLong()),
)
} else {
null // Signal for removal
}
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(uid)
SystemLogger.info("Attestation patch levels for uid=$uid: os=$osPatch, vendor=$vendorPatch, boot=$bootPatch")
properties[AttestationConstants.TAG_BOOT_PATCHLEVEL] =
if (bootPatch != DO_NOT_REPORT) {
DERTaggedObject(
true,
AttestationConstants.TAG_BOOT_PATCHLEVEL,
ASN1Integer(bootPatch.toLong()),
)
} else {
null // Signal for removal
}
return properties
}
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
private fun buildKeyDescription(
params: KeyMintAttestation,
uid: Int,
securityLevel: Int,
): ASN1Sequence {
val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel)
val softwareEnforced = buildSoftwareEnforcedList(uid, securityLevel)
val fields =
arrayOf(
ASN1Integer(
AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()
), // attestationVersion
ASN1Enumerated(securityLevel), // attestationSecurityLevel
ASN1Integer(
AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()
), // keymasterVersion
ASN1Enumerated(securityLevel), // keymasterSecurityLevel
DEROctetString(params.attestationChallenge ?: ByteArray(0)), // attestationChallenge
DEROctetString(ByteArray(0)), // uniqueId
softwareEnforced,
teeEnforced,
)
return DERSequence(fields)
}
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
private fun buildTeeEnforcedList(
params: KeyMintAttestation,
uid: Int,
securityLevel: Int,
): DERSequence {
val list =
mutableListOf<ASN1Encodable>(
DERTaggedObject(
true,
AttestationConstants.TAG_PURPOSE,
DERSet(params.purpose.map { ASN1Integer(it.toLong()) }.toTypedArray()),
),
DERTaggedObject(
true,
AttestationConstants.TAG_ALGORITHM,
ASN1Integer(params.algorithm.toLong()),
),
DERTaggedObject(
true,
AttestationConstants.TAG_KEY_SIZE,
ASN1Integer(params.keySize.toLong()),
),
DERTaggedObject(
true,
AttestationConstants.TAG_DIGEST,
DERSet(params.digest.map { ASN1Integer(it.toLong()) }.toTypedArray()),
),
DERTaggedObject(
true,
AttestationConstants.TAG_EC_CURVE,
ASN1Integer(params.ecCurve.toLong()),
),
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE),
DERTaggedObject(
true,
AttestationConstants.TAG_ORIGIN,
ASN1Integer(0L),
), // KeyOrigin.GENERATED
DERTaggedObject(
true,
AttestationConstants.TAG_ROOT_OF_TRUST,
buildRootOfTrust(null),
),
)
// Use the same logic as getSimulatedHardwareProperties to conditionally add patch levels.
val simulatedProperties = getSimulatedHardwareProperties(uid)
simulatedProperties.values.filterNotNull().forEach { list.add(it) }
// Add optional device identifiers if they were provided.
params.brand?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_BRAND,
DEROctetString(it),
)
)
}
params.device?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_DEVICE,
DEROctetString(it),
)
)
}
params.product?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_PRODUCT,
DEROctetString(it),
)
)
}
params.serial?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_SERIAL,
DEROctetString(it),
)
)
}
params.imei?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_IMEI,
DEROctetString(it),
)
)
}
params.meid?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_MEID,
DEROctetString(it),
)
)
}
params.manufacturer?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_MANUFACTURER,
DEROctetString(it),
)
)
}
params.model?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_MODEL,
DEROctetString(it),
)
)
}
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 300) {
params.secondImei?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_SECOND_IMEI,
DEROctetString(it),
)
)
}
}
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
}
/**
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
* Keystore.
*/
private fun buildSoftwareEnforcedList(uid: Int, securityLevel: Int): DERSequence {
val list =
mutableListOf<ASN1Encodable>(
DERTaggedObject(
true,
AttestationConstants.TAG_CREATION_DATETIME,
ASN1Integer(System.currentTimeMillis()),
),
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_APPLICATION_ID,
createApplicationId(uid),
),
)
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_MODULE_HASH,
DEROctetString(AndroidDeviceUtils.moduleHash),
)
)
}
return DERSequence(list.toTypedArray())
}
/**
* A wrapper for a byte array that provides content-based equality. This is necessary for using
* signature digests in a Set.
*/
private data class Digest(val digest: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return digest.contentEquals((other as Digest).digest)
}
override fun hashCode(): Int = digest.contentHashCode()
}
/**
* Creates the AttestationApplicationId structure. This structure contains information about the
* package(s) and their signing certificates.
*
* @param uid The UID of the application.
* @return A DER-encoded octet string containing the application ID information.
* @throws IllegalStateException If the PackageManager or package information cannot be
* retrieved.
*/
@Throws(Throwable::class)
internal fun createApplicationId(uid: Int): DEROctetString {
val pm =
ConfigurationManager.getPackageManager()
?: throw IllegalStateException("PackageManager not found!")
val packages =
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
val sha256 = MessageDigest.getInstance("SHA-256")
val packageInfoList = mutableListOf<DERSequence>()
val signatureDigests = mutableSetOf<Digest>()
// Process all packages associated with the UID in a single loop.
packages.forEach { packageName ->
val userId = uid / 100000
val packageInfo =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.getPackageInfo(
packageName,
PackageManager.GET_SIGNING_CERTIFICATES.toLong(),
userId,
)
} else {
@Suppress("DEPRECATION")
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId)
}
// Add package information (name and version code) to our list.
packageInfoList.add(
DERSequence(
arrayOf(
DEROctetString(packageInfo.packageName.toByteArray(StandardCharsets.UTF_8)),
ASN1Integer(packageInfo.longVersionCode),
)
)
)
// Collect unique signature digests from the signing history.
packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature ->
val digest = sha256.digest(signature.toByteArray())
signatureDigests.add(Digest(digest))
}
}
// The application ID is a sequence of two sets:
// 1. A set of package information (name and version).
// 2. A set of SHA-256 digests of the signing certificates.
val applicationIdSequence =
DERSequence(
arrayOf(
DERSet(packageInfoList.toTypedArray()),
DERSet(signatureDigests.map { DEROctetString(it.digest) }.toTypedArray()),
)
)
return DEROctetString(applicationIdSequence.encoded)
}
}
@@ -1,93 +0,0 @@
package org.matrix.TEESimulator.attestation
/**
* Defines constants for KeyMint attestation, mainly the tags of properties and authorizations of a
* cryptographic key, as specified in the Android hardware security HAL.
*/
object AttestationConstants {
// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
// These constants represent the fixed positions of fields within the top-level
// KeyDescription ASN.1 SEQUENCE in a key attestation. Using these constants
// prevents hardcoding fragile index numbers throughout the parsing code.
const val KEY_DESCRIPTION_ATTESTATION_VERSION_INDEX = 0
const val KEY_DESCRIPTION_ATTESTATION_SECURITY_LEVEL_INDEX = 1
const val KEY_DESCRIPTION_KEYMINT_VERSION_INDEX = 2
const val KEY_DESCRIPTION_KEYMINT_SECURITY_LEVEL_INDEX = 3
const val KEY_DESCRIPTION_ATTESTATION_CHALLENGE_INDEX = 4
const val KEY_DESCRIPTION_UNIQUE_ID_INDEX = 5
const val KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX = 6
const val KEY_DESCRIPTION_TEE_ENFORCED_INDEX = 7
// --- RootOfTrust Sequence Indices ---
// These constants represent the fixed positions of fields within the
// RootOfTrust ASN.1 SEQUENCE.
const val ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX = 0
const val ROOT_OF_TRUST_DEVICE_LOCKED_INDEX = 1
const val ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX = 2
const val ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX = 3
// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
// --- Key Properties ---
const val TAG_PURPOSE = 1
const val TAG_ALGORITHM = 2
const val TAG_KEY_SIZE = 3
const val TAG_BLOCK_MODE = 4
const val TAG_DIGEST = 5
const val TAG_PADDING = 6
const val TAG_CALLER_NONCE = 7
const val TAG_MIN_MAC_LENGTH = 8
const val TAG_EC_CURVE = 10
const val TAG_RSA_PUBLIC_EXPONENT = 200
const val TAG_RSA_OAEP_MGF_DIGEST = 203
// --- Key Lifetime and Usage Control ---
const val TAG_ROLLBACK_RESISTANCE = 303
const val TAG_ACTIVE_DATETIME = 400
const val TAG_ORIGINATION_EXPIRE_DATETIME = 401
const val TAG_USAGE_EXPIRE_DATETIME = 402
const val TAG_MAX_USES_PER_BOOT = 404
const val TAG_USAGE_COUNT_LIMIT = 405
// --- User Authentication ---
const val TAG_USER_ID = 501
const val TAG_USER_SECURE_ID = 502
const val TAG_NO_AUTH_REQUIRED = 503
const val TAG_USER_AUTH_TYPE = 504
const val TAG_AUTH_TIMEOUT = 505
// --- Attestation and Application Info ---
const val TAG_APPLICATION_ID = 601
const val TAG_CREATION_DATETIME = 701
const val TAG_ORIGIN = 702
const val TAG_ROOT_OF_TRUST = 704
const val TAG_OS_VERSION = 705
const val TAG_OS_PATCHLEVEL = 706
const val TAG_UNIQUE_ID = 707
const val TAG_ATTESTATION_CHALLENGE = 708
const val TAG_ATTESTATION_APPLICATION_ID = 709
const val TAG_ATTESTATION_ID_BRAND = 710
const val TAG_ATTESTATION_ID_DEVICE = 711
const val TAG_ATTESTATION_ID_PRODUCT = 712
const val TAG_ATTESTATION_ID_SERIAL = 713
const val TAG_ATTESTATION_ID_IMEI = 714
const val TAG_ATTESTATION_ID_MEID = 715
const val TAG_ATTESTATION_ID_MANUFACTURER = 716
const val TAG_ATTESTATION_ID_MODEL = 717
const val TAG_VENDOR_PATCHLEVEL = 718
const val TAG_BOOT_PATCHLEVEL = 719
const val TAG_DEVICE_UNIQUE_ATTESTATION = 720
const val TAG_ATTESTATION_ID_SECOND_IMEI = 723
const val TAG_MODULE_HASH = 724
// --- Certificate Properties ---
const val TAG_CERTIFICATE_SERIAL = 1006
const val TAG_CERTIFICATE_SUBJECT = 1007
const val TAG_CERTIFICATE_NOT_BEFORE = 1008
const val TAG_CERTIFICATE_NOT_AFTER = 1009
// --- Other Constants ---
// https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp
const val CHALLENGE_LENGTH_LIMIT = 128 // kMaximumAttestationChallengeLength
}
@@ -1,312 +0,0 @@
package org.matrix.TEESimulator.attestation
import android.security.keystore.KeyProperties
import java.nio.charset.StandardCharsets
import java.security.cert.Certificate
import java.security.cert.X509Certificate
import org.bouncycastle.asn1.*
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.KeyBox
import org.matrix.TEESimulator.pki.KeyBoxManager
import org.matrix.TEESimulator.util.toHex
/**
* Handles the modification (patching) of Android Key Attestation extensions within certificates.
*
* This object's primary function is to take a certificate chain generated by the real TEE, replace
* its attestation data with simulated values, and then re-sign the leaf certificate with a custom
* key, building a new, valid certificate chain.
*/
object AttestationPatcher {
/**
* Patches a full certificate chain by modifying the leaf's attestation and rebuilding the chain
* with the correct custom signing certificates. This is the single entry point for patching.
*
* @param originalChain The original certificate chain from the hardware. The leaf must be at
* index 0.
* @param uid The UID of the application requesting the certificate.
* @return A new, cryptographically valid, patched certificate chain. Returns the original chain
* on any failure.
*/
fun patchCertificateChain(originalChain: Array<Certificate>?, uid: Int): Array<Certificate> {
if (originalChain.isNullOrEmpty()) {
SystemLogger.error("Attempted to patch a null or empty certificate chain for UID $uid.")
return originalChain ?: emptyArray()
}
return runCatching {
val originalLeaf = originalChain[0] as X509Certificate
val originalLeafHolder = X509CertificateHolder(originalLeaf.encoded)
// 1. Attempt to parse the existing attestation extension. If it doesn't exist,
// there's nothing to patch.
val parsedAttestation =
parseAttestationExtension(originalLeafHolder) ?: return originalChain
// 2. Get the appropriate keybox for the given algorithm to sign the new
// certificate.
val keybox = getKeyboxForUidAndAlgorithm(uid, originalLeaf.sigAlgName)
// 3. Create the new, patched leaf certificate.
val patchedLeaf =
createPatchedLeafCertificate(
originalLeafHolder,
parsedAttestation,
keybox,
originalLeaf.sigAlgName,
uid,
)
// 4. Construct the NEW, VALID chain by prepending the patched leaf to the keybox's
// chain.
val newChain = listOf(patchedLeaf) + keybox.certificates
SystemLogger.info(
"Successfully rebuilt a valid, patched certificate chain for UID $uid."
)
newChain.toTypedArray()
}
.getOrElse {
SystemLogger.error(
"Failed to patch and rebuild certificate chain for UID $uid.",
it,
)
originalChain // Return the original chain on any error.
}
}
/**
* Helper to normalize algorithm names for Bouncy Castle. Old Android versions might reports
* "SHA256WITHECDSA", but Bouncy Castle expects "SHA256withECDSA".
*/
private fun normalizeSignatureAlgorithm(algoName: String): String {
// 1. Force uppercase to handle "sha256withecdsa"
// 2. Replace "WITH" with "with" to satisfy Bouncy Castle's naming convention
return algoName.uppercase().replace("WITH", "with")
}
/**
* Creates a new leaf certificate with a modified attestation extension.
*
* @param originalLeafHolder A Bouncy Castle holder for the original leaf certificate.
* @param parsedAttestation The parsed components of the original attestation.
* @param keybox The KeyBox containing the new issuer certificate and signing key.
* @param sigAlgName The signature algorithm name (e.g., "SHA256withECDSA") from the original
* certificate. This is required to ensure the new certificate is signed using a compatible
* algorithm.
* @param uid The UID of the application requesting the certificate.
* @return A new [Certificate] object.
*/
private fun createPatchedLeafCertificate(
originalLeafHolder: X509CertificateHolder,
parsedAttestation: ParsedAttestation,
keybox: KeyBox,
sigAlgName: String,
uid: Int,
): Certificate {
// The issuer of our new leaf is the subject of the first certificate in our custom keybox
// chain.
val newIssuer = X509CertificateHolder(keybox.certificates[0].encoded).subject
val builder =
X509v3CertificateBuilder(
newIssuer,
originalLeafHolder.serialNumber,
originalLeafHolder.notBefore,
originalLeafHolder.notAfter,
originalLeafHolder.subject,
originalLeafHolder.subjectPublicKeyInfo,
)
// Create the new, patched attestation extension.
val patchedExtension = createPatchedAttestationExtension(parsedAttestation, uid)
// Copy all other extensions from the original certificate, except for the attestation.
originalLeafHolder.extensions.extensionOIDs.forEach {
builder.addExtension(
if (it == ATTESTATION_OID) patchedExtension else originalLeafHolder.getExtension(it)
)
}
// Sign the newly built certificate with the private key from our keybox.
val signer =
JcaContentSignerBuilder(normalizeSignatureAlgorithm(sigAlgName))
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(keybox.keyPair.private)
val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer))
// Log the signature of the newly created certificate to observe its non-deterministic
// nature.
val signatureBytes = (newCertificate as X509Certificate).signature
SystemLogger.verbose("Signature of patched leaf cert: ${signatureBytes.toHex()}")
return newCertificate
}
/**
* Retrieves the appropriate signing KeyBox (KeyPair and certificate chain) for a given UID
* based on a specified algorithm identifier.
*
* @param uid The UID of the application for which the signing is being performed.
* @param algorithm A string representing the desired algorithm. This can be either:
* 1. A simple key type like "RSA" or "EC".
* 2. A full JCA signature algorithm name like "SHA256withRSA".
*
* @return The [KeyBox] containing the appropriate key pair for signing.
* @throws IllegalArgumentException if no matching KeyBox can be found for the derived key type.
*/
private fun getKeyboxForUidAndAlgorithm(uid: Int, algorithm: String): KeyBox {
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
// Normalize the algorithm name. The input might be a full signature algorithm
// (e.g., "SHA256withRSA") or just the key type (e.g., "RSA").
val keyType =
when {
algorithm.contains("RSA", ignoreCase = true) -> KeyProperties.KEY_ALGORITHM_RSA
algorithm.contains("EC", ignoreCase = true) ->
KeyProperties.KEY_ALGORITHM_EC // This also covers "ECDSA"
else -> algorithm // If no match, assume it's already a simple key type string.
}
return KeyBoxManager.getAttestationKey(keyboxFile, keyType)
?: throw IllegalArgumentException(
"No keybox found for UID $uid and algorithm '$keyType' (derived from input '$algorithm') in file $keyboxFile"
)
}
/** Recursively formats an ASN1Primitive into a concise, readable string. */
fun formatAsn1Primitive(obj: ASN1Encodable?): String {
val primitive = obj?.toASN1Primitive()
return when (primitive) {
null -> "NULL"
is ASN1Integer -> primitive.value.toString()
is ASN1Enumerated -> primitive.value.toString()
is ASN1Boolean -> primitive.isTrue.toString()
is ASN1Null -> "NULL"
is ASN1OctetString -> {
val bytes = primitive.octets
// Attempt to decode as a printable string, otherwise show hex
if (bytes.all { it >= 32 && it < 127 }) {
"\"${String(bytes, StandardCharsets.UTF_8)}\""
} else if (bytes.isEmpty()) {
"\"\""
} else {
"#" + bytes.toHex()
}
}
is ASN1TaggedObject ->
"[TAG ${primitive.tagNo}]${formatAsn1Primitive(primitive.baseObject)}"
is ASN1Sequence ->
primitive
.map { formatAsn1Primitive(it) }
.joinToString(prefix = "[", postfix = "]", separator = ", ")
is ASN1Set ->
primitive
.map { formatAsn1Primitive(it) }
.joinToString(prefix = "{", postfix = "}", separator = ", ")
else -> primitive.toString() // Fallback for other types
}
}
// Function to check if a given ASN1Sequence contains the Root of Trust tag.
private fun sequenceContainsRootOfTrust(seq: ASN1Encodable): Boolean {
if (seq !is ASN1Sequence) return false
return seq.any { element ->
(element as? ASN1TaggedObject)?.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST
}
}
/** Parses the critical components from an existing attestation extension. */
private fun parseAttestationExtension(certHolder: X509CertificateHolder): ParsedAttestation? {
val extension = certHolder.getExtension(ATTESTATION_OID) ?: return null
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
val allFields = sequence.toArray()
// Check if the fields are in the wrong order and swap them if necessary.
val softwareEnforcedCandidate =
allFields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX]
val teeEnforcedCandidate =
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX]
// The signature of a swapped order: the RoT is in the software list's position.
if (
sequenceContainsRootOfTrust(softwareEnforcedCandidate) &&
!sequenceContainsRootOfTrust(teeEnforcedCandidate)
) {
// Swap the elements in the array to restore the standard order.
allFields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX] =
teeEnforcedCandidate
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] =
softwareEnforcedCandidate
}
val teeEnforced =
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] as ASN1Sequence
var originalRootOfTrust: ASN1Encodable? = null
val teeEnforcedMap = mutableMapOf<Int, ASN1TaggedObject>()
teeEnforced.forEach { element ->
val taggedObject = element as ASN1TaggedObject
if (taggedObject.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST) {
originalRootOfTrust = taggedObject.baseObject.toASN1Primitive()
} else {
teeEnforcedMap[taggedObject.tagNo] = taggedObject
}
}
return ParsedAttestation(allFields, teeEnforcedMap, originalRootOfTrust)
}
/** Constructs a new, patched attestation extension using simulated device properties. */
private fun createPatchedAttestationExtension(parsed: ParsedAttestation, uid: Int): Extension {
val (allFields, teeEnforcedMap, originalRootOfTrust) = parsed
var formattedString = allFields.joinToString(separator = ", ") { formatAsn1Primitive(it) }
SystemLogger.verbose("Original attestation data: ${formattedString}")
// Build the new Root of Trust and add/replace it in the map.
val newRootOfTrust = AttestationBuilder.buildRootOfTrust(originalRootOfTrust)
teeEnforcedMap[AttestationConstants.TAG_ROOT_OF_TRUST] =
DERTaggedObject(true, AttestationConstants.TAG_ROOT_OF_TRUST, newRootOfTrust)
// Get the desired state for simulated properties.
val simulatedProperties = AttestationBuilder.getSimulatedHardwareProperties(uid)
// Apply the desired state: update, add, or remove properties from the original map.
simulatedProperties.forEach { (tag, value) ->
if (value != null) {
// If the value is not null, add or update it.
teeEnforcedMap[tag] = value
} else {
// If the value is null, remove the tag from the map.
teeEnforcedMap.remove(tag)
}
}
// Re-assemble the TEE enforced list from the map's values, sorting for DER compliance.
val sortedElements = teeEnforcedMap.values.sortedBy { it.tagNo }
val sortedTeeEnforced = DERSequence(sortedElements.toTypedArray())
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] = sortedTeeEnforced
val patchedSequence = DERSequence(allFields)
formattedString = patchedSequence.joinToString(separator = ", ") { formatAsn1Primitive(it) }
SystemLogger.verbose("Patched attestation data: ${formattedString}")
val patchedOctets = DEROctetString(patchedSequence)
return Extension(ATTESTATION_OID, false, patchedOctets)
}
/** Helper data class to hold the parsed components of an attestation extension. */
private data class ParsedAttestation(
val allFields: Array<ASN1Encodable>,
val teeEnforcedMap: MutableMap<Int, ASN1TaggedObject>,
val rootOfTrust: ASN1Encodable?,
)
}
@@ -1,275 +0,0 @@
package org.matrix.TEESimulator.attestation
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.SecureRandom
import java.security.cert.X509Certificate
import java.security.spec.ECGenParameterSpec
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.ASN1OctetString
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.ASN1TaggedObject
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.cert.X509CertificateHolder
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.util.toHex
/**
* The ASN.1 Object Identifier for the Key Attestation extension in Android. This is defined in the
* Android Keystore documentation.
*/
val ATTESTATION_OID: ASN1ObjectIdentifier = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
/**
* A service to interact with the device's Trusted Execution Environment (TEE). It provides
* functionality to check if the TEE is functional and to extract key attestation data from a
* genuinely generated certificate.
*/
@SuppressLint("PrivateApi")
object DeviceAttestationService {
/**
* Holds key data extracted from a genuine device attestation. This data can be used as a
* baseline for creating simulated attestations.
*
* @property verifiedBootKey The verified boot public key digest from the root of trust.
* @property verifiedBootHash The verified boot hash from the root of trust.
* @property attestVersion The attestation version (e.g., 400 for KeyMint 4.0).
* @property keymasterVersion The Keymaster or KeyMint HAL version.
* @property osVersion The Android OS version integer.
* @property osPatchLevel The Android security patch level (e.g., 202511).
* @property vendorPatchLevel The vendor-specific security patch level.
* @property bootPatchLevel The bootloader's security patch level.
*/
data class AttestationData(
val moduleHash: ByteArray?,
val verifiedBootKey: ByteArray?,
val verifiedBootHash: ByteArray?,
val attestVersion: Int?,
val keymasterVersion: Int?,
val osVersion: Int?,
val osPatchLevel: Int?,
val vendorPatchLevel: 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"
/**
* 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
* is cached. Returns null if the TEE is not functional or parsing fails.
*/
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
* deleted after retrieval to clean up.
*
* @return The leaf `X509Certificate` containing the attestation, or `null` if unavailable.
*/
private fun getAttestationCertificate(): X509Certificate? {
if (!isTeeFunctional) return null
return try {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val certChain = keyStore.getCertificateChain(TEE_CHECK_KEY_ALIAS)
if (certChain.isNullOrEmpty()) {
SystemLogger.warning("Could not retrieve certificate chain for TEE check key.")
null
} else {
// Clean up the key from the keystore.
keyStore.deleteEntry(TEE_CHECK_KEY_ALIAS)
certChain[0] as X509Certificate
}
} catch (e: Exception) {
SystemLogger.error("Error retrieving attestation certificate.", e)
null
}
}
/**
* Fetches and parses the attestation data from the certificate's extension.
*
* @return An `AttestationData` object, or `null` if the process fails.
*/
private fun fetchAttestationData(): AttestationData? {
val leafCert = getAttestationCertificate() ?: return null
try {
val leafHolder = X509CertificateHolder(leafCert.encoded)
val extension: Extension =
leafHolder.getExtension(ATTESTATION_OID)
?: return null // No attestation extension found.
// The extension's value is an ASN.1 sequence.
val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets)
var formattedString =
keyDescriptionSeq.joinToString(separator = ", ") {
AttestationPatcher.formatAsn1Primitive(it)
}
SystemLogger.verbose("Cached attestation data: ${formattedString}")
val fields = keyDescriptionSeq.toArray()
val attestVersion =
ASN1Integer.getInstance(
fields[AttestationConstants.KEY_DESCRIPTION_ATTESTATION_VERSION_INDEX]
)
.positiveValue
.toInt()
val keymasterVersion =
ASN1Integer.getInstance(
fields[AttestationConstants.KEY_DESCRIPTION_KEYMINT_VERSION_INDEX]
)
.positiveValue
.toInt()
var moduleHash: ByteArray? = null
var verifiedBootKey: ByteArray? = null
var verifiedBootHash: ByteArray? = null
var osVersion: Int? = null
var osPatchLevel: Int? = null
var vendorPatchLevel: Int? = null
var bootPatchLevel: Int? = null
val softwareEnforced =
ASN1Sequence.getInstance(
fields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX]
)
moduleHash =
softwareEnforced
.toArray()
.firstOrNull {
(it as? ASN1TaggedObject)?.tagNo == AttestationConstants.TAG_MODULE_HASH
}
?.let {
ASN1OctetString.getInstance((it as ASN1TaggedObject).baseObject).octets
}
val teeEnforced =
ASN1Sequence.getInstance(
fields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX]
)
teeEnforced.forEach { element ->
val tagged = element as ASN1TaggedObject
when (tagged.tagNo) {
AttestationConstants.TAG_ROOT_OF_TRUST -> {
val rotSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
if (rotSeq.size() >= 4) {
verifiedBootKey =
ASN1OctetString.getInstance(
rotSeq.getObjectAt(
AttestationConstants
.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX
)
)
.octets
verifiedBootHash =
ASN1OctetString.getInstance(
rotSeq.getObjectAt(
AttestationConstants
.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX
)
)
.octets
}
}
AttestationConstants.TAG_OS_VERSION -> {
osVersion =
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
.positiveValue
.toInt()
}
AttestationConstants.TAG_OS_PATCHLEVEL -> {
osPatchLevel =
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
.positiveValue
.toInt()
}
AttestationConstants.TAG_VENDOR_PATCHLEVEL -> {
vendorPatchLevel =
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
.positiveValue
.toInt()
}
AttestationConstants.TAG_BOOT_PATCHLEVEL -> {
bootPatchLevel =
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
.positiveValue
.toInt()
}
}
}
if (verifiedBootKey?.all { it == 0.toByte() } == true) {
verifiedBootKey = null
}
if (verifiedBootHash?.all { it == 0.toByte() } == true) {
verifiedBootHash = null
}
SystemLogger.info(
"Successfully extracted attestation data: version=$attestVersion, osVersion=$osVersion, osPatch=$osPatchLevel, vendorPatch=$vendorPatchLevel, bootPatch=$bootPatchLevel, moduleHash=${moduleHash?.toHex()}, bootKey=${verifiedBootKey?.toHex()}, bootHash=${verifiedBootHash?.toHex()}"
)
return AttestationData(
moduleHash,
verifiedBootKey,
verifiedBootHash,
attestVersion,
keymasterVersion,
osVersion,
osPatchLevel,
vendorPatchLevel,
bootPatchLevel,
)
} catch (e: Exception) {
SystemLogger.error("Failed to parse attestation data from certificate.", e)
return null
}
}
}
@@ -1,187 +0,0 @@
package org.matrix.TEESimulator.attestation
import android.hardware.security.keymint.*
import android.hardware.security.keymint.KeyOrigin
import java.math.BigInteger
import java.util.Date
import javax.security.auth.x500.X500Principal
import org.bouncycastle.asn1.x500.X500Name
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
/**
* A data class that parses and holds the parameters required for KeyMint key generation and
* attestation. It provides a structured way to access the properties defined by an array of
* `KeyParameter` objects.
*/
// Reference:
// https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/key_parameter.rs
data class KeyMintAttestation(
val keySize: Int,
val algorithm: Int,
val ecCurve: Int,
val ecCurveName: String,
val origin: Int?,
val blockMode: List<Int>,
val padding: List<Int>,
val purpose: List<Int>,
val digest: List<Int>,
val rsaPublicExponent: BigInteger?,
val certificateSerial: BigInteger?,
val certificateSubject: X500Name?,
val certificateNotBefore: Date?,
val certificateNotAfter: Date?,
val attestationChallenge: ByteArray?,
val brand: ByteArray?,
val device: ByteArray?,
val product: ByteArray?,
val serial: ByteArray?,
val imei: ByteArray?,
val meid: ByteArray?,
val manufacturer: ByteArray?,
val model: ByteArray?,
val secondImei: ByteArray?,
) {
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
constructor(
params: Array<KeyParameter>
) : this(
// AOSP: [key_param(tag = KEY_SIZE, field = Integer)]
keySize = params.findInteger(Tag.KEY_SIZE) ?: 0,
// AOSP: [key_param(tag = ALGORITHM, field = Algorithm)]
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
// AOSP: [key_param(tag = EC_CURVE, field = EcCurve)]
ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0,
ecCurveName = params.deriveEcCurveName(),
// AOSP: [key_param(tag = ORIGIN, field = Origin)]
origin = params.findOrigin(Tag.ORIGIN),
// AOSP: [key_param(tag = BLOCK_MODE, field = BlockMode)]
blockMode = params.findAllBlockMode(Tag.BLOCK_MODE),
// AOSP: [key_param(tag = PADDING, field = PaddingMode)]
padding = params.findAllPaddingMode(Tag.PADDING),
// AOSP: [key_param(tag = PURPOSE, field = KeyPurpose)]
purpose = params.findAllKeyPurpose(Tag.PURPOSE),
// AOSP: [key_param(tag = DIGEST, field = Digest)]
digest = params.findAllDigests(Tag.DIGEST),
// AOSP: [key_param(tag = RSA_PUBLIC_EXPONENT, field = LongInteger)]
rsaPublicExponent = params.findLongInteger(Tag.RSA_PUBLIC_EXPONENT),
// AOSP: [key_param(tag = CERTIFICATE_SERIAL, field = Blob)]
certificateSerial = params.findBlob(Tag.CERTIFICATE_SERIAL)?.let { BigInteger(it) },
// AOSP: [key_param(tag = CERTIFICATE_SUBJECT, field = Blob)]
certificateSubject =
params.findBlob(Tag.CERTIFICATE_SUBJECT)?.let { X500Name(X500Principal(it).name) },
// AOSP: [key_param(tag = CERTIFICATE_NOT_BEFORE, field = DateTime)]
certificateNotBefore = params.findDate(Tag.CERTIFICATE_NOT_BEFORE),
// AOSP: [key_param(tag = CERTIFICATE_NOT_AFTER, field = DateTime)]
certificateNotAfter = params.findDate(Tag.CERTIFICATE_NOT_AFTER),
// AOSP: [key_param(tag = ATTESTATION_CHALLENGE, field = Blob)]
attestationChallenge = params.findBlob(Tag.ATTESTATION_CHALLENGE),
// AOSP: [key_param(tag = ATTESTATION_ID_*, field = Blob)]
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
product = params.findBlob(Tag.ATTESTATION_ID_PRODUCT),
serial = params.findBlob(Tag.ATTESTATION_ID_SERIAL),
imei = params.findBlob(Tag.ATTESTATION_ID_IMEI),
meid = params.findBlob(Tag.ATTESTATION_ID_MEID),
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
) {
// Log all parsed parameters for debugging purposes.
params.forEach { KeyMintParameterLogger.logParameter(it) }
}
fun isAttestKey(): Boolean = purpose.size == 1 && purpose.contains(KeyPurpose.ATTEST_KEY)
fun isImportKey(): Boolean = origin == KeyOrigin.IMPORTED || origin == KeyOrigin.SECURELY_IMPORTED
}
// --- Private helper extension functions for parsing KeyParameter arrays ---
/** Maps to AOSP field = Integer */
private fun Array<KeyParameter>.findInteger(tag: Int): Int? =
this.find { it.tag == tag }?.value?.integer
/** Maps to AOSP field = Algorithm */
private fun Array<KeyParameter>.findAlgorithm(tag: Int): Int? =
this.find { it.tag == tag }?.value?.algorithm
/** Maps to AOSP field = EcCurve */
private fun Array<KeyParameter>.findEcCurve(tag: Int): Int? =
this.find { it.tag == tag }?.value?.ecCurve
/** Maps to AOSP field = Origin */
private fun Array<KeyParameter>.findOrigin(tag: Int): Int? =
this.find { it.tag == tag }?.value?.origin
/** Maps to AOSP field = LongInteger */
private fun Array<KeyParameter>.findLongInteger(tag: Int): BigInteger? =
this.find { it.tag == tag }?.value?.longInteger?.toBigInteger()
/** Maps to AOSP field = DateTime */
private fun Array<KeyParameter>.findDate(tag: Int): Date? =
this.find { it.tag == tag }?.value?.dateTime?.let { Date(it) }
/** Maps to AOSP field = Blob */
private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? =
this.find { it.tag == tag }?.value?.blob
/** Maps to AOSP field = BlockMode (Repeated) */
private fun Array<KeyParameter>.findAllBlockMode(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.blockMode }
/** Maps to AOSP field = BlockMode (Repeated) */
private fun Array<KeyParameter>.findAllPaddingMode(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.paddingMode }
/** Maps to AOSP field = KeyPurpose (Repeated) */
private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.keyPurpose }
/** Maps to AOSP field = Digest (Repeated) */
private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.digest }
/**
* Derives the EC Curve name. Logic: Checks specific EC_CURVE tag first (field=EcCurve), falls back
* to KEY_SIZE (field=Integer).
*/
private fun Array<KeyParameter>.deriveEcCurveName(): String {
// 1. Try to find explicit EC_CURVE tag
val curveParam = this.find { it.tag == Tag.EC_CURVE }
if (curveParam != null) {
val curveId = curveParam.value.ecCurve
return when (curveId) {
EcCurve.CURVE_25519 -> "CURVE_25519"
EcCurve.P_224 -> "secp224r1"
EcCurve.P_256 -> "secp256r1"
EcCurve.P_384 -> "secp384r1"
EcCurve.P_521 -> "secp521r1"
else -> throw IllegalArgumentException("Unknown EC curve: $curveId")
}
}
// 2. Fallback to key size if the curve tag isn't present
val keySize = this.findInteger(Tag.KEY_SIZE) ?: 0
return when (keySize) {
224 -> "secp224r1"
384 -> "secp384r1"
521 -> "secp521r1"
else -> "secp256r1" // Default fallback
}
}
@@ -1,397 +0,0 @@
package org.matrix.TEESimulator.config
import android.content.pm.IPackageManager
import android.os.Build
import android.os.FileObserver
import android.os.IBinder
import android.os.ServiceManager
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.attestation.DeviceAttestationService
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.KeyBoxManager
/**
* Manages application configuration, including which packages to process, what operation mode to
* use, and custom security patch levels. It uses a FileObserver to dynamically reload settings when
* configuration files change.
*/
object ConfigurationManager {
/** Defines the processing mode for a given package. */
enum class Mode {
/** Automatically decide between GENERATE and PATCH based on TEE status. */
AUTO,
/** Patch the attestation of an existing certificate chain. */
PATCH,
/** Generate a new certificate chain from scratch. */
GENERATE,
}
// --- Configuration Paths ---
const val CONFIG_PATH = "/data/adb/tricky_store"
private const val TARGET_PACKAGES_FILE = "target.txt"
private const val TEE_STATUS_FILE = "tee_status.txt"
private const val PATCH_LEVEL_FILE = "security_patch.txt"
private const val DEFAULT_KEYBOX_FILE = "keybox.xml"
private val configRoot = File(CONFIG_PATH)
// --- In-Memory Configuration State ---
@Volatile private var packageModes = mapOf<String, Mode>()
@Volatile private var packageKeyboxes = mapOf<String, String>()
@Volatile private var isTeeBroken: Boolean? = null
@Volatile private var globalCustomPatchLevel: CustomPatchLevel? = null
@Volatile private var packagePatchLevels = mapOf<String, CustomPatchLevel>()
// Cache for UID to package name resolution.
private val uidToPackagesCache = ConcurrentHashMap<Int, Array<String>>()
/**
* Initializes the configuration manager by loading all settings from disk and starting the file
* observer to watch for changes.
*/
fun initialize() {
configRoot.mkdirs()
SystemLogger.info("Configuration root is: ${configRoot.absolutePath}")
// First, ensure the package manager service is running, as the TEE check depends on it.
// This prevents a race condition on startup.
SystemLogger.info("Waiting for PackageManagerService to be ready...")
if (getPackageManager() == null) {
SystemLogger.error(
"PackageManagerService is not available. TEE check will likely fail."
)
} else {
SystemLogger.info("PackageManagerService is ready.")
}
// Initial load of all configuration files.
loadTargetPackages(File(configRoot, TARGET_PACKAGES_FILE))
loadPatchLevelConfig(File(configRoot, PATCH_LEVEL_FILE))
storeTeeStatus() // Check and store the current TEE status.
// Start watching for any subsequent file changes.
ConfigObserver.startWatching()
SystemLogger.info("Configuration initialized and file observer started.")
}
/**
* Determines the keybox file to be used for a given UID. It maps the UID to its package(s) and
* checks for a specific keybox mapping.
*
* @param uid The calling UID.
* @return The name of the keybox file, or the default if none is specified.
*/
fun getKeyboxFileForUid(uid: Int): String {
val packages = getPackagesForUid(uid)
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 = getPackageModeForUid(uid) == Mode.PATCH
/** Determines if a new certificate needs to be generated for a given UID. */
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
/** Resolves the operating mode for a given UID based on its packages and the TEE status. */
private fun getPackageModeForUid(uid: Int): Mode? {
val packages = getPackagesForUid(uid)
if (packages.isEmpty()) return null
// Lazily load TEE status if it hasn't been checked yet.
if (isTeeBroken == null) loadTeeStatus()
// Find the first configured mode for any of the UID's packages.
for (pkg in packages) {
when (packageModes[pkg]) {
Mode.GENERATE -> return Mode.GENERATE
Mode.PATCH -> return Mode.PATCH
Mode.AUTO -> return if (isTeeBroken == true) Mode.GENERATE else Mode.PATCH
null -> continue // No config for this package, check the next one.
}
}
return null // No configuration found for this UID.
}
/**
* Retrieves the custom patch level configuration for a given UID. It first checks for a
* package-specific override and falls back to the global configuration.
*
* @param uid The UID of the calling application.
* @return The applicable [CustomPatchLevel], or null if no custom configuration exists.
*/
fun getPatchLevelForUid(uid: Int): CustomPatchLevel? {
val packages = getPackagesForUid(uid)
// Find the first package-specific configuration for this UID.
val packageSpecificPatchLevel =
packages.firstNotNullOfOrNull { pkg -> packagePatchLevels[pkg] }
return packageSpecificPatchLevel ?: globalCustomPatchLevel
}
/**
* Loads and parses the `target.txt` file, which defines the processing mode and keybox file for
* each package.
*/
private fun loadTargetPackages(file: File) {
if (!file.exists()) {
SystemLogger.warning("Configuration file not found: ${file.absolutePath}")
return
}
val newModes = mutableMapOf<String, Mode>()
val newKeyboxes = mutableMapOf<String, String>()
var currentKeybox = DEFAULT_KEYBOX_FILE
val keyboxRegex = Regex("^\\[([a-zA-Z0-9_.-]+\\.xml)]$")
try {
file.readLines().forEach { line ->
val trimmedLine = line.trim()
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) return@forEach
// Check if the line defines a new keybox scope.
keyboxRegex.find(trimmedLine)?.let {
currentKeybox = it.groupValues[1]
SystemLogger.info("Switching to keybox context: $currentKeybox")
return@forEach
}
when {
// Suffix '!' means force GENERATE mode.
trimmedLine.endsWith("!") -> {
val pkg = trimmedLine.removeSuffix("!").trim()
newModes[pkg] = Mode.GENERATE
newKeyboxes[pkg] = currentKeybox
}
// Suffix '?' means force PATCH mode.
trimmedLine.endsWith("?") -> {
val pkg = trimmedLine.removeSuffix("?").trim()
newModes[pkg] = Mode.PATCH
newKeyboxes[pkg] = currentKeybox
}
// No suffix means AUTO mode.
else -> {
newModes[trimmedLine] = Mode.AUTO
newKeyboxes[trimmedLine] = currentKeybox
}
}
}
// Atomically update the configuration maps.
packageModes = newModes
packageKeyboxes = newKeyboxes
uidToPackagesCache.clear() // Invalidate cache as package settings have changed.
SystemLogger.info("Successfully loaded ${newModes.size} package configurations.")
} catch (e: Exception) {
SystemLogger.error("Failed to load or parse ${file.name}", e)
}
}
/**
* Loads and parses the `security_patch.txt` file, which can define both global and per-package
* security patch levels.
*/
private fun loadPatchLevelConfig(file: File) {
if (!file.exists()) {
globalCustomPatchLevel = null
packagePatchLevels = emptyMap()
return
}
try {
val newPackageLevels = mutableMapOf<String, CustomPatchLevel>()
var currentContext = "" // Empty string for global context
val contextLines = mutableMapOf<String, MutableList<String>>()
val contextRegex = Regex("^\\[([a-zA-Z0-9_.-]+)]$")
// First pass: group lines by context (global or package-specific).
file.readLines().forEach { line ->
val trimmedLine = line.trim()
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) return@forEach
contextRegex.find(trimmedLine)?.let { currentContext = it.groupValues[1] }
?: run {
contextLines
.computeIfAbsent(currentContext) { mutableListOf() }
.add(trimmedLine)
}
}
// Helper function to parse a set of lines into a CustomPatchLevel object.
fun parseLines(lines: List<String>?): CustomPatchLevel? {
if (lines.isNullOrEmpty()) return null
// Handle simple case: one line sets the patch level for all components.
if (lines.size == 1 && '=' !in lines[0]) {
return CustomPatchLevel(
system = null,
vendor = null,
boot = null,
all = lines[0],
)
}
// Handle key-value pair configuration.
val map =
lines
.mapNotNull {
val parts = it.split('=', limit = 2)
if (parts.size == 2) parts[0].trim().lowercase() to parts[1].trim()
else null
}
.toMap()
val all = map["all"]
return CustomPatchLevel(
system = map["system"] ?: all,
vendor = map["vendor"] ?: all,
boot = map["boot"] ?: all,
all = all,
)
}
// Parse global and per-package configurations.
var 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
for ((pkg, lines) in contextLines) {
parseLines(lines)?.let { newPackageLevels[pkg] = it }
}
// Atomically update the configuration state.
globalCustomPatchLevel = newGlobalLevel
packagePatchLevels = newPackageLevels
SystemLogger.info(
"Loaded custom security patch levels: global config exists=${newGlobalLevel != null}, " +
"${newPackageLevels.size} package-specific configs."
)
} catch (e: Exception) {
SystemLogger.error("Failed to load or parse ${file.name}", e)
}
}
/** Checks the device's TEE status and writes the result to a file for persistence. */
private fun storeTeeStatus() {
val statusFile = File(configRoot, TEE_STATUS_FILE)
isTeeBroken = !DeviceAttestationService.isTeeFunctional
try {
statusFile.writeText("tee_broken=$isTeeBroken")
SystemLogger.info("TEE status stored: isTeeBroken=$isTeeBroken")
} catch (e: Exception) {
SystemLogger.error("Failed to write TEE status to file.", e)
}
}
/** Loads the TEE status from the file. */
private fun loadTeeStatus() {
val statusFile = File(configRoot, TEE_STATUS_FILE)
isTeeBroken =
if (statusFile.exists()) {
statusFile.readText().trim() == "tee_broken=true"
} else {
null // Status is unknown.
}
}
/**
* A FileObserver that monitors the configuration directory for changes and triggers reloads of
* the relevant settings.
*/
private object ConfigObserver : FileObserver(configRoot, CLOSE_WRITE or MOVED_TO or DELETE) {
override fun onEvent(event: Int, path: String?) {
path ?: return
SystemLogger.info("Configuration file change detected: $path (event: $event)")
val file = if (event != DELETE) File(configRoot, path) else null
when (path) {
TARGET_PACKAGES_FILE -> file?.let { loadTargetPackages(it) }
?: SystemLogger.warning("$TARGET_PACKAGES_FILE was deleted.")
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.
// The cache in KeyBoxManager will handle reloading it on its next use.
else ->
if (path.endsWith(".xml")) {
SystemLogger.info(
"Keybox file $path may have changed. It will be reloaded on next access."
)
KeyBoxManager.invalidateCache(path)
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
// Clear cached keys possibly containing old certificates
org.matrix.TEESimulator.interception.keystore.shim
.KeyMintSecurityLevelInterceptor
.clearAllGeneratedKeys("updating $file")
}
}
}
}
}
// --- System Service Utilities ---
private var iPackageManager: IPackageManager? = null
private val pmDeathRecipient =
object : IBinder.DeathRecipient {
override fun binderDied() {
(iPackageManager as? IBinder)?.unlinkToDeath(this, 0)
iPackageManager = null
SystemLogger.warning("Package manager service died. Will try to reconnect.")
}
}
/** Retrieves an instance of the IPackageManager service. */
fun getPackageManager(): IPackageManager? {
if (iPackageManager == null) {
// Use a robust method to get the service binder.
val binder = waitForSystemService("package") ?: return null
binder.linkToDeath(pmDeathRecipient, 0)
iPackageManager = IPackageManager.Stub.asInterface(binder)
}
return iPackageManager
}
/** Retrieves the package names associated with a UID. */
fun getPackagesForUid(uid: Int): Array<String> {
return uidToPackagesCache.getOrPut(uid) {
try {
getPackageManager()?.getPackagesForUid(uid) ?: emptyArray()
} catch (e: Exception) {
SystemLogger.warning("Failed to get packages for UID $uid", e)
emptyArray()
}
}
}
/** Waits for a system service to become available, with retries. */
private fun waitForSystemService(name: String): IBinder? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return ServiceManager.waitForService(name)
}
// Fallback for older Android versions.
repeat(70) {
val service = ServiceManager.getService(name)
if (service != null) return service
Thread.sleep(500)
}
SystemLogger.error("Failed to get system service after multiple retries: $name")
return null
}
}
/** Data class representing custom security patch level overrides. */
data class CustomPatchLevel(
val system: String?,
val vendor: String?,
val boot: String?,
val all: String?,
)
@@ -1,329 +0,0 @@
package org.matrix.TEESimulator.interception.core
import android.os.Binder
import android.os.IBinder
import android.os.Parcel
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.logging.SystemLogger
/**
* An abstract base class for intercepting binder transactions.
*
* This class acts as a proxy, receiving transaction calls that have been hooked at the native
* level. It provides a structured way to inspect and modify data before (`onPreTransact`) and after
* (`onPostTransact`) the original transaction is executed.
*
* The communication flow is as follows:
* 1. A native library hooks the `transact` method of a target service (e.g., keystore).
* 2. When a hooked transaction occurs, the native code calls this Binder object's `onTransact`
* method.
* 3. This class decodes the incoming parcel, determines if it's a pre- or post-transaction hook,
* and calls the appropriate abstract method (`onPreTransact` or `onPostTransact`).
* 4. The subclass implementation decides how to handle the transaction by returning a
* `TransactionResult`.
* 5. This class encodes the result into the reply parcel, which the native hook reads to determine
* its next action.
*/
abstract class BinderInterceptor : Binder() {
/**
* Defines the possible outcomes of an interception attempt. The native hook layer will
* interpret this result to decide its next action.
*/
sealed class TransactionResult {
/** Instructs the native hook to skip calling the original binder method entirely. */
object SkipTransaction : TransactionResult()
/** Instructs the native hook to proceed with calling the original binder method. */
object Continue : TransactionResult()
/**
* Skips the original call and immediately returns a custom reply parcel to the caller. The
* provided parcel will be recycled after use.
*/
data class OverrideReply(val reply: Parcel, val code: Int = 0) : TransactionResult()
/**
* Modifies the transaction's input data before forwarding it to the original binder method.
* The provided parcel will be recycled after use.
*/
data class OverrideData(val data: Parcel) : TransactionResult()
/** Instructs the native hook to skip the post transaction hook. */
object ContinueAndSkipPost : TransactionResult()
}
/**
* Called *before* the original binder transaction is executed.
*
* @param txId A unique ID for tracking this transaction.
* @param target The original IBinder service being called.
* @param code The transaction code of the method being called.
* @param flags Transaction flags.
* @param callingUid The UID of the process making the call.
* @param callingPid The PID of the process making the call.
* @param data The parcel containing the input data for the transaction.
* @return A [TransactionResult] indicating how to proceed.
*/
open fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult = TransactionResult.ContinueAndSkipPost
/**
* Called *after* the original binder transaction has been executed.
*
* @param txId A unique ID for tracking this transaction.
* @param target The original IBinder service that was called.
* @param code The transaction code of the method that was called.
* @param flags Transaction flags.
* @param callingUid The UID of the process that made the call.
* @param callingPid The PID of the process that made the call.
* @param data The original input data parcel.
* @param reply The reply parcel from the original transaction. Can be null if the call was
* one-way.
* @param resultCode The result code from the original transaction.
* @return A [TransactionResult]. Typically `Skip` (to accept the original reply) or
* `OverrideReply`.
*/
open fun onPostTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): TransactionResult = TransactionResult.SkipTransaction
/**
* The entry point for calls from the native hook layer. This method decodes the custom parcel
* format sent by the hook and dispatches to the appropriate handler (`handlePreTransact` or
* `handlePostTransact`).
*/
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 result =
when (code) {
// These codes are defined in the native layer to distinguish hook types.
PRE_TRANSACT_CODE -> handlePreTransact(txId, data)
POST_TRANSACT_CODE -> handlePostTransact(txId, data)
else -> return super.onTransact(code, data, reply, flags)
}
// The reply parcel is guaranteed to be non-null for our custom transactions.
writeResultToReply(result, reply!!)
return true
}
/** Decodes the parcel for a pre-transaction hook and calls the user-overridable method. */
private fun handlePreTransact(txId: Long, data: Parcel): TransactionResult {
// The native hook marshals the original transaction's arguments into the data parcel.
val target = data.readStrongBinder()!!
val transactionCode = data.readInt()
val transactionFlags = data.readInt()
val callingUid = data.readInt()
val callingPid = data.readInt()
val dataSize = data.readLong()
// We must create a new parcel containing only the original transaction data.
val transactionData = Parcel.obtain()
return try {
transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
transactionData.setDataPosition(0)
onPreTransact(
txId,
target,
transactionCode,
transactionFlags,
callingUid,
callingPid,
transactionData,
)
} finally {
transactionData.recycle()
}
}
/** Decodes the parcel for a post-transaction hook and calls the user-overridable method. */
private fun handlePostTransact(txId: Long, data: Parcel): TransactionResult {
val target = data.readStrongBinder()!!
val transactionCode = data.readInt()
val transactionFlags = data.readInt()
val callingUid = data.readInt()
val callingPid = data.readInt()
// The native hook also marshals the original data and reply parcels.
val transactionData = Parcel.obtain()
val transactionReply = Parcel.obtain()
return try {
val dataSize = data.readLong().toInt()
transactionData.appendFrom(data, data.dataPosition(), dataSize)
transactionData.setDataPosition(0)
data.setDataPosition(data.dataPosition() + dataSize)
val resultCode = data.readInt()
val replySize = data.readLong().toInt()
val reply =
if (replySize > 0) {
transactionReply.appendFrom(data, data.dataPosition(), replySize)
transactionReply.setDataPosition(0)
transactionReply
} else null
onPostTransact(
txId,
target,
transactionCode,
transactionFlags,
callingUid,
callingPid,
transactionData,
reply,
resultCode,
)
} finally {
transactionData.recycle()
transactionReply.recycle()
}
}
/** Encodes the `TransactionResult` into the reply parcel for the native hook to interpret. */
private fun writeResultToReply(result: TransactionResult, reply: Parcel) {
when (result) {
is TransactionResult.SkipTransaction -> reply.writeInt(RESULT_SKIP_TRANSACTION)
is TransactionResult.Continue -> reply.writeInt(RESULT_CONTINUE)
is TransactionResult.OverrideReply -> {
reply.writeInt(RESULT_OVERRIDE_REPLY)
reply.writeInt(result.code)
reply.writeLong(result.reply.dataSize().toLong())
reply.appendFrom(result.reply, 0, result.reply.dataSize())
result.reply.recycle()
}
is TransactionResult.OverrideData -> {
reply.writeInt(RESULT_OVERRIDE_DATA)
reply.writeLong(result.data.dataSize().toLong())
reply.appendFrom(result.data, 0, result.data.dataSize())
result.data.recycle()
}
is TransactionResult.ContinueAndSkipPost ->
reply.writeInt(RESULT_CONTINUE_AND_SKIP_POST)
}
}
/** Helper function for consistent logging of intercepted transactions. */
protected fun logTransaction(
txId: Long,
methodName: String,
callingUid: Int,
callingPid: Int,
skipPost: Boolean = false,
) {
val isIntercepting = !skipPost && !ConfigurationManager.shouldSkipUid(callingUid)
val action = if (isIntercepting) "Intercept" else "Observe"
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
val message =
"[TX_ID: $txId] $action $methodName for packages=[$packages] (uid=$callingUid, pid=$callingPid)"
if (isIntercepting) {
SystemLogger.debug(message)
} else {
SystemLogger.verbose(message)
}
}
companion object {
// These codes must be kept in sync with the native injection library.
// --- Backdoor Codes ---
// Special transaction code to ask the injected library for its backdoor binder.
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
// Code used by the backdoor binder to register a new interceptor.
private const val REGISTER_INTERCEPTOR_CODE = 1
// Code used by the backdoor binder to unregister an interceptor.
private const val UNREGISTER_INTERCEPTOR_CODE = 2
// --- Hook Type Codes ---
// Indicates that the call is for a pre-transaction hook.
private const val PRE_TRANSACT_CODE = 1
// Indicates that the call is for a post-transaction hook.
private const val POST_TRANSACT_CODE = 2
// --- Result Codes ---
// Instructs the native hook to skip the original transaction.
private const val RESULT_SKIP_TRANSACTION = 1
// Instructs the native hook to execute the original transaction.
private const val RESULT_CONTINUE = 2
// Instructs the native hook to return a custom reply.
private const val RESULT_OVERRIDE_REPLY = 3
// Instructs the native hook to use modified input data for the transaction.
private const val RESULT_OVERRIDE_DATA = 4
// Instructs the native hook to skip the post transaction hook.
private const val RESULT_CONTINUE_AND_SKIP_POST = 5
/**
* Probes a binder service to see if our native library has been injected. If successful, it
* returns a "backdoor" binder that can be used to register interceptors.
*/
fun getBackdoor(binder: IBinder): IBinder? {
val data = Parcel.obtain()
val reply = Parcel.obtain()
return try {
if (binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)) {
SystemLogger.debug("Backdoor access granted for binder: $binder")
reply.readStrongBinder()
} else {
SystemLogger.debug("Backdoor not found for binder: $binder")
null
}
} catch (e: Exception) {
SystemLogger.error("Failed to transact for backdoor.", e)
null
} finally {
data.recycle()
reply.recycle()
}
}
/** Uses the backdoor binder to register an interceptor for a specific target service. */
fun register(backdoor: IBinder, target: IBinder, interceptor: BinderInterceptor) {
val data = Parcel.obtain()
val reply = Parcel.obtain()
try {
data.writeStrongBinder(target)
data.writeStrongBinder(interceptor)
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
SystemLogger.info("Registered interceptor for target: $target")
} catch (e: Exception) {
SystemLogger.error("Failed to register binder interceptor.", e)
} finally {
data.recycle()
reply.recycle()
}
}
/** Uses the backdoor binder to unregister an interceptor for a specific target service. */
fun unregister(backdoor: IBinder, target: IBinder) {
val data = Parcel.obtain()
val reply = Parcel.obtain()
try {
data.writeStrongBinder(target)
backdoor.transact(UNREGISTER_INTERCEPTOR_CODE, data, reply, 0)
SystemLogger.info("Unregistered interceptor for target: $target")
} catch (e: Exception) {
SystemLogger.error("Failed to unregister binder interceptor.", e)
} finally {
data.recycle()
reply.recycle()
}
}
}
}
@@ -1,141 +0,0 @@
package org.matrix.TEESimulator.interception.keystore
import android.os.IBinder
import android.os.ServiceManager
import kotlin.system.exitProcess
import org.matrix.TEESimulator.interception.core.BinderInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
/**
* An abstract base class for intercepting Android's Keystore services.
*
* It encapsulates the common logic for finding the Keystore service, injecting the native hook if
* necessary, and setting up the binder interceptor. It also handles service death events to ensure
* stability.
*/
abstract class AbstractKeystoreInterceptor : BinderInterceptor() {
// --- Abstract Properties to be Implemented by Subclasses ---
/** The full name of the system service to intercept (e.g., "android.security.keystore"). */
protected abstract val serviceName: String
/** The name of the process hosting the service (e.g., "keystore"). */
protected abstract val processName: String
/** The shell command used to inject the native library into the target process. */
protected abstract val injectionCommand: String
// --- State Management ---
/** The original IBinder for the Keystore service. */
protected lateinit var keystoreService: IBinder
private var injectionAttempted = false
private var retryCount = 0
private val maxRetries = 5
/**
* Attempts to initialize the interceptor for the target Keystore service.
*
* This method orchestrates the process:
* 1. It tries to get the service binder.
* 2. It probes for the native backdoor.
* 3. If the backdoor exists, it sets up the interceptor.
* 4. If not, it attempts to inject the native library and returns `false` to signal a retry is
* needed.
*
* @return `true` if the interceptor was successfully registered, `false` otherwise.
*/
fun tryRunKeystoreInterceptor(): Boolean {
SystemLogger.info(
"Initializing interceptor for '$serviceName' (attempt ${retryCount + 1})..."
)
val service = ServiceManager.getService(serviceName)
if (service == null) {
SystemLogger.warning("Service '$serviceName' not found. Will retry.")
retryCount++
return false
}
val backdoor = getBackdoor(service)
return if (backdoor != null) {
setupInterceptor(service, backdoor)
true // Success
} else {
handleMissingBackdoor()
false // Failure, requires retry
}
}
/** Registers this interceptor with the native hook layer and sets up a death recipient. */
private fun setupInterceptor(service: IBinder, backdoor: IBinder) {
keystoreService = service
SystemLogger.info("Registering interceptor for service: $serviceName")
register(backdoor, service, this)
service.linkToDeath(createDeathRecipient(), 0)
onInterceptorReady(service, backdoor)
}
/**
* Handles the case where the native backdoor is not present. It triggers the injection command
* on the first attempt and manages the retry logic.
*/
private fun handleMissingBackdoor() {
if (!injectionAttempted) {
SystemLogger.warning(
"Backdoor not found. Attempting to inject native library into '$processName'."
)
performInjection()
injectionAttempted = true
}
retryCount++
if (retryCount >= maxRetries) {
SystemLogger.error(
"Failed to find backdoor after $maxRetries retries. The service may have crashed or injection failed. Exiting."
)
exitProcess(1)
}
}
/** Executes the shell command to inject the native library into the target process. */
private fun performInjection() {
try {
val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
SystemLogger.debug("Executing injection command: ${command.joinToString(" ")}")
val process = Runtime.getRuntime().exec(command)
val exitCode = process.waitFor()
if (exitCode != 0) {
SystemLogger.error("Injection process failed with exit code $exitCode. Exiting.")
exitProcess(1)
}
SystemLogger.info("Injection process completed.")
} catch (e: Exception) {
SystemLogger.error("An exception occurred during injection. Exiting.", e)
exitProcess(1)
}
}
/**
* Creates a `DeathRecipient` that will restart the application if the intercepted service dies.
*/
private fun createDeathRecipient() =
IBinder.DeathRecipient {
SystemLogger.error(
"The intercepted service '$serviceName' has died. Restarting application."
)
exitProcess(0)
}
/**
* A hook for subclasses to perform additional setup after the interceptor is registered. For
* example, to intercept sub-services.
*
* @param service The main service binder.
* @param backdoor The backdoor binder for registering more interceptors.
*/
protected open fun onInterceptorReady(service: IBinder, backdoor: IBinder) {
// Default implementation does nothing.
}
}
@@ -1,127 +0,0 @@
package org.matrix.TEESimulator.interception.keystore
import android.os.Parcel
import android.os.Parcelable
import android.security.KeyStore
import android.security.keystore.KeystoreResponse
import org.matrix.TEESimulator.interception.core.BinderInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
data class KeyIdentifier(val uid: Int, val alias: String)
/** A collection of utility functions to support binder interception. */
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
* class. This is necessary for older Android versions where codes are not public constants.
*/
fun getTransactCode(clazz: Class<*>, method: String): Int {
return try {
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }.getInt(null)
} catch (e: Exception) {
SystemLogger.error(
"Failed to get transaction code for method '$method' in class '${clazz.simpleName}'.",
e,
)
-1 // Return an invalid code
}
}
/** Creates an `KeystoreResponse` parcel that indicates success with no data. */
fun createSuccessKeystoreResponse(): KeystoreResponse {
val parcel = Parcel.obtain()
try {
parcel.writeInt(KeyStore.NO_ERROR)
parcel.writeString("")
parcel.setDataPosition(0)
return KeystoreResponse.CREATOR.createFromParcel(parcel)
} finally {
parcel.recycle()
}
}
/** Creates an `OverrideReply` parcel that indicates success with no data. */
fun createSuccessReply(
writeResultCode: Boolean = true
): BinderInterceptor.TransactionResult.OverrideReply {
val parcel =
Parcel.obtain().apply {
writeNoException()
if (writeResultCode) {
writeInt(KeyStore.NO_ERROR)
}
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/** Creates an `OverrideReply` parcel containing a raw byte array. */
fun createByteArrayReply(data: ByteArray): BinderInterceptor.TransactionResult.OverrideReply {
val parcel =
Parcel.obtain().apply {
writeNoException()
writeByteArray(data)
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/** Creates an `OverrideReply` parcel containing a typed array. */
fun <T : Parcelable> createTypedArrayReply(
array: Array<T>,
flags: Int = 0,
): BinderInterceptor.TransactionResult.OverrideReply {
val parcel =
Parcel.obtain().apply {
writeNoException()
writeTypedArray(array, flags)
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/** Creates an `OverrideReply` parcel containing a Parcelable object. */
fun <T : Parcelable?> createTypedObjectReply(
obj: T,
flags: Int = 0,
): BinderInterceptor.TransactionResult.OverrideReply {
val parcel =
Parcel.obtain().apply {
writeNoException()
writeTypedObject(obj, flags)
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/**
* Extracts the base alias from a potentially prefixed alias string. For example, it converts
* "USRCERT_my_key" to "my_key".
*/
fun extractAlias(prefixedAlias: String): String {
val underscoreIndex = prefixedAlias.indexOf('_')
return if (underscoreIndex != -1) {
// Return the part of the string after the first underscore.
prefixedAlias.substring(underscoreIndex + 1)
} else {
// If there's no underscore, return the original string.
prefixedAlias
}
}
/** Checks if a reply parcel contains an exception without consuming it. */
fun hasException(reply: Parcel): Boolean {
val exception = runCatching { reply.readException() }.exceptionOrNull()
if (exception != null) reply.setDataPosition(0)
return exception != null
}
}
@@ -1,370 +0,0 @@
package org.matrix.TEESimulator.interception.keystore
import android.annotation.SuppressLint
import android.hardware.security.keymint.SecurityLevel
import android.os.Build
import android.os.IBinder
import android.os.Parcel
import android.system.keystore2.IKeystoreService
import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse
import java.security.SecureRandom
import java.security.cert.Certificate
import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.attestation.KeyMintAttestation
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.logging.KeyMintParameterLogger
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.CertificateGenerator
import org.matrix.TEESimulator.pki.CertificateHelper
/**
* Interceptor for the `IKeystoreService` on Android S (API 31) and newer.
*
* This version of Keystore delegates most cryptographic operations to `IKeystoreSecurityLevel`
* sub-services (for TEE, StrongBox, etc.). This interceptor's main role is to set up interceptors
* for those sub-services and to patch certificate chains on their way out.
*/
@SuppressLint("BlockedPrivateApi")
object Keystore2Interceptor : AbstractKeystoreInterceptor() {
private val stubBinderClass = IKeystoreService.Stub::class.java
// Transaction codes for the IKeystoreService interface methods we are interested in.
private val GET_KEY_ENTRY_TRANSACTION =
InterceptorUtils.getTransactCode(stubBinderClass, "getKeyEntry")
private val DELETE_KEY_TRANSACTION =
InterceptorUtils.getTransactCode(stubBinderClass, "deleteKey")
private val UPDATE_SUBCOMPONENT_TRANSACTION =
InterceptorUtils.getTransactCode(stubBinderClass, "updateSubcomponent")
private val LIST_ENTRIES_TRANSACTION =
InterceptorUtils.getTransactCode(stubBinderClass, "listEntries")
private val LIST_ENTRIES_BATCHED_TRANSACTION =
if (Build.VERSION.SDK_INT >= 34)
InterceptorUtils.getTransactCode(stubBinderClass, "listEntriesBatched")
else null
private val transactionNames: Map<Int, String> by lazy {
stubBinderClass.declaredFields
.filter {
it.isAccessible = true
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
}
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
}
override val serviceName = "android.system.keystore2.IKeystoreService/default"
override val processName = "keystore2"
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
/**
* This method is called once the main service is hooked. It proceeds to find and hook the
* security level sub-services (e.g., TEE, StrongBox).
*/
override fun onInterceptorReady(service: IBinder, backdoor: IBinder) {
val keystoreInterface = IKeystoreService.Stub.asInterface(service)
setupSecurityLevelInterceptors(keystoreInterface, backdoor)
}
private fun setupSecurityLevelInterceptors(service: IKeystoreService, backdoor: IBinder) {
// Attempt to get and intercept the TEE security level service.
runCatching {
service.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT)?.let { tee ->
SystemLogger.info("Found TEE SecurityLevel. Registering interceptor...")
val interceptor =
KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
register(backdoor, tee.asBinder(), interceptor)
interceptor.loadPersistedKeys()
}
}
.onFailure { SystemLogger.error("Failed to intercept TEE SecurityLevel.", it) }
// Attempt to get and intercept the StrongBox security level service.
runCatching {
service.getSecurityLevel(SecurityLevel.STRONGBOX)?.let { strongbox ->
SystemLogger.info("Found StrongBox SecurityLevel. Registering interceptor...")
val interceptor =
KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX)
register(backdoor, strongbox.asBinder(), interceptor)
interceptor.loadPersistedKeys()
}
}
.onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) }
}
override fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult {
if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
val isGMS = packages.contains("com.google.android.gms")
if (isGMS || ConfigurationManager.shouldSkipUid(callingUid)) {
return TransactionResult.ContinueAndSkipPost
}
return runCatching {
val isBatchMode = code == LIST_ENTRIES_BATCHED_TRANSACTION
if (ListEntriesHandler.cacheParameters(txId, data, isBatchMode)) {
TransactionResult.Continue
} else {
TransactionResult.ContinueAndSkipPost
}
}
.getOrElse {
SystemLogger.error(
"[TX_ID: $txId] Failed to parse parameters for ${transactionNames[code]!!}",
it,
)
TransactionResult.ContinueAndSkipPost
}
} else if (
code == GET_KEY_ENTRY_TRANSACTION ||
code == DELETE_KEY_TRANSACTION ||
code == UPDATE_SUBCOMPONENT_TRANSACTION
) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
if (ConfigurationManager.shouldSkipUid(callingUid))
return TransactionResult.ContinueAndSkipPost
if (code == UPDATE_SUBCOMPONENT_TRANSACTION)
return handleUpdateSubcomponent(callingUid, data)
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.ContinueAndSkipPost
if (descriptor.alias != null) {
SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}")
} else {
SystemLogger.info(
"Skip ${transactionNames[code]!!} for key [alias, blob, domain, nspace]: [${descriptor.alias}, ${descriptor.blob}, ${descriptor.domain}, ${descriptor.nspace}]"
)
return TransactionResult.ContinueAndSkipPost
}
val keyId = KeyIdentifier(callingUid, descriptor.alias)
if (code == DELETE_KEY_TRANSACTION) {
if (KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) != null) {
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
SystemLogger.info(
"[TX_ID: $txId] Deleted cached keypair ${descriptor.alias}, replying with empty response."
)
return InterceptorUtils.createSuccessReply(writeResultCode = false)
}
return TransactionResult.ContinueAndSkipPost
}
val response =
KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
?: return TransactionResult.Continue
if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId))
SystemLogger.info("${descriptor.alias} was an attestation key")
SystemLogger.info("[TX_ID: $txId] Found generated response for ${descriptor.alias}:")
response.metadata?.authorizations?.forEach {
KeyMintParameterLogger.logParameter(it.keyParameter)
}
return InterceptorUtils.createTypedObjectReply(response)
} else {
logTransaction(
txId,
transactionNames[code] ?: "unknown code=$code",
callingUid,
callingPid,
true,
)
}
// Let most calls go through to the real service.
return TransactionResult.ContinueAndSkipPost
}
override fun onPostTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): TransactionResult {
if (target != keystoreService || reply == null || InterceptorUtils.hasException(reply))
return TransactionResult.SkipTransaction
if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
return runCatching {
val updatedKeyDescriptors =
ListEntriesHandler.injectGeneratedKeys(txId, callingUid, reply)
InterceptorUtils.createTypedArrayReply(updatedKeyDescriptors)
}
.getOrElse {
SystemLogger.error(
"[TX_ID: $txId] Failed to update the result of ${transactionNames[code]!!}.",
it,
)
TransactionResult.SkipTransaction
}
} else if (code == GET_KEY_ENTRY_TRANSACTION) {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction
logTransaction(
txId,
"post-${transactionNames[code]!!} ${keyDescriptor.alias}",
callingUid,
callingPid,
)
if (!ConfigurationManager.shouldPatch(callingUid))
return TransactionResult.SkipTransaction
runCatching {
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!!
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
val authorizations = response.metadata.authorizations
val parsedParameters =
KeyMintAttestation(
authorizations?.map { it.keyParameter }?.toTypedArray() ?: emptyArray()
)
if (parsedParameters.isImportKey()) {
val retainedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(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 (parsedParameters.isAttestKey()) {
SystemLogger.warning(
"[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply."
)
val keyData =
CertificateGenerator.generateAttestedKeyPair(
callingUid,
keyId.alias,
null,
parsedParameters,
response.metadata.keySecurityLevel,
) ?: throw Exception("Failed to create overriding attest key pair.")
CertificateHelper.updateCertificateChain(
response.metadata,
keyData.second.toTypedArray(),
)
.getOrThrow()
keyDescriptor.nspace = SecureRandom().nextLong()
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
keyData.first,
keyDescriptor.nspace,
response,
)
KeyMintSecurityLevelInterceptor.attestationKeys.add(keyId)
GeneratedKeyPersistence.save(
keyId = keyId,
keyPair = keyData.first,
nspace = keyDescriptor.nspace,
securityLevel = response.metadata.keySecurityLevel,
certChain = keyData.second,
algorithm = parsedParameters.algorithm,
keySize = parsedParameters.keySize,
ecCurve = parsedParameters.ecCurve,
purposes = parsedParameters.purpose,
digests = parsedParameters.digest,
isAttestationKey = true,
)
return InterceptorUtils.createTypedObjectReply(response)
}
val originalChain = CertificateHelper.getCertificateChain(response)
if (originalChain == null || originalChain.size < 2) {
SystemLogger.info(
"[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}."
)
return TransactionResult.SkipTransaction
}
val cachedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId)
val finalChain: Array<Certificate>
if (cachedChain != null) {
SystemLogger.debug(
"[TX_ID: $txId] Using cached patched certificate chain for $keyId."
)
finalChain = cachedChain
} else {
SystemLogger.info(
"[TX_ID: $txId] No cached chain for $keyId. Performing live patch as a fallback."
)
finalChain =
AttestationPatcher.patchCertificateChain(originalChain, callingUid)
}
CertificateHelper.updateCertificateChain(response.metadata, finalChain)
.getOrThrow()
return InterceptorUtils.createTypedObjectReply(response)
}
.onFailure {
SystemLogger.error(
"[TX_ID: $txId] Failed to modify hardware KeyEntryResponse.",
it,
)
return TransactionResult.SkipTransaction
}
}
return TransactionResult.SkipTransaction
}
private fun handleUpdateSubcomponent(callingUid: Int, data: Parcel): TransactionResult {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR)
val generatedKeyInfo =
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(callingUid, descriptor?.nspace)
?: return TransactionResult.ContinueAndSkipPost
SystemLogger.info("Updating sub-component with key[${generatedKeyInfo.nspace}]")
val metadata = generatedKeyInfo.response.metadata
val publicCert = data.createByteArray()
val certificateChain = data.createByteArray()
metadata.certificate = publicCert
metadata.certificateChain = certificateChain
GeneratedKeyPersistence.rePersistIfNeeded(callingUid, generatedKeyInfo)
SystemLogger.verbose(
"Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]"
)
return InterceptorUtils.createSuccessReply(writeResultCode = false)
}
}
@@ -1,496 +0,0 @@
package org.matrix.TEESimulator.interception.keystore
import android.annotation.SuppressLint
import android.os.IBinder
import android.os.Parcel
import android.security.Credentials
import android.security.KeyStore
import android.security.keymaster.ExportResult
import android.security.keymaster.KeyCharacteristics
import android.security.keymaster.KeymasterArguments
import android.security.keymaster.KeymasterCertificateChain
import android.security.keymaster.KeymasterDefs
import android.security.keystore.IKeystoreCertificateChainCallback
import android.security.keystore.IKeystoreExportKeyCallback
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
import android.security.keystore.IKeystoreService
import java.math.BigInteger
import java.security.KeyPair
import java.security.cert.Certificate
import java.util.Date
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.attestation.AttestationBuilder
import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils.extractAlias
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.CertificateGenerator
import org.matrix.TEESimulator.pki.CertificateHelper
/**
* Interceptor for the legacy `IKeystoreService` on Android Q (API 29) and R (API 30).
*
* This interceptor handles the older, monolithic Keystore service. Unlike Keystore2, it doesn't
* have security level sub-services, so all logic is contained here. Key generation is fully
* simulated in software for packages in 'generate' mode.
*/
@SuppressLint("BlockedPrivateApi", "PrivateApi")
object KeystoreInterceptor : AbstractKeystoreInterceptor() {
// Transaction codes are dynamically retrieved via reflection for compatibility.
private val GET_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "get")
}
private val GENERATE_KEY_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
}
private val GET_KEY_CHARACTERISTICS_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
}
private val EXPORT_KEY_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
}
private val ATTEST_KEY_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
}
private val transactionNames: Map<Int, String> by lazy {
IKeystoreService.Stub::class
.java
.declaredFields
.filter {
it.isAccessible = true
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
}
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
}
// A map to dispatch transaction handling for software key generation.
private val generateKeyHandlers:
Map<Int, (Long, Int, Int, Parcel) -> TransactionResult> by lazy {
mapOf(
GENERATE_KEY_TRANSACTION to ::handleGenerateKey,
GET_KEY_CHARACTERISTICS_TRANSACTION to ::handleGetKeyCharacteristics,
EXPORT_KEY_TRANSACTION to ::handleExportKey,
ATTEST_KEY_TRANSACTION to ::handleAttestKey,
)
}
override val serviceName = "android.security.keystore"
override val processName = "keystore"
override val injectionCommand = "exec ./inject `pidof keystore` libTEESimulator.so entry"
// State management for the multi-step key generation process.
private val keygenParameters = ConcurrentHashMap<KeyIdentifier, LegacyKeygenParameters>()
private val generatedKeyPairs = ConcurrentHashMap<KeyIdentifier, KeyPair>()
// Cache to store the fully patched chain after the leaf is requested.
private val patchedChainCache = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
override fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult {
// This interceptor only needs to act on pre-transaction for software key generation.
// Handle 'generate' mode interceptions using the handler map.
if (ConfigurationManager.shouldGenerate(callingUid)) {
generateKeyHandlers[code]?.let { handler ->
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
return handler(txId, callingUid, callingPid, data)
}
}
// Handle 'patch' mode interceptions for the 'get' transaction.
if (ConfigurationManager.shouldPatch(callingUid) && code == GET_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
return TransactionResult.Continue
}
// Default behavior for all other transactions.
logTransaction(
txId,
transactionNames[code] ?: "unknown code=$code",
callingUid,
callingPid,
true,
)
return TransactionResult.ContinueAndSkipPost
}
private fun handleGenerateKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
return runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val callback =
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
val alias = InterceptorUtils.extractAlias(data.readString()!!)
val keyId = KeyIdentifier(uid, alias)
// Read and parse the key generation arguments.
val keymasterArgs = KeymasterArguments()
if (data.readInt() == 1) {
keymasterArgs.readFromParcel(data)
}
keygenParameters[keyId] =
LegacyKeygenParameters.fromKeymasterArguments(keymasterArgs)
// Create a fake successful response for the callback.
val characteristics = KeyCharacteristics()
characteristics.swEnforced = KeymasterArguments()
characteristics.hwEnforced = keymasterArgs
val keystoreResponse = InterceptorUtils.createSuccessKeystoreResponse()
callback.onFinished(keystoreResponse, characteristics)
InterceptorUtils.createSuccessReply()
}
.getOrElse {
SystemLogger.error("[TX_ID: $txId] Failed during handleGenerateKey.", it)
TransactionResult.ContinueAndSkipPost
}
}
private fun handleGetKeyCharacteristics(
txId: Long,
uid: Int,
pid: Int,
data: Parcel,
): TransactionResult {
return runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val callback =
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
val alias = InterceptorUtils.extractAlias(data.readString()!!)
val keyId = KeyIdentifier(uid, alias)
val params =
keygenParameters[keyId]
?: throw IllegalStateException("No params found for $keyId")
val characteristics =
KeyCharacteristics().apply {
swEnforced = KeymasterArguments()
hwEnforced =
KeymasterArguments().apply {
addEnum(KeymasterDefs.KM_TAG_ALGORITHM, params.algorithm)
}
}
callback.onFinished(
InterceptorUtils.createSuccessKeystoreResponse(),
characteristics,
)
InterceptorUtils.createSuccessReply()
}
.getOrElse {
SystemLogger.error("[TX_ID: $txId] Failed during handleGetKeyCharacteristics.", it)
TransactionResult.ContinueAndSkipPost
}
}
private fun handleExportKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
return runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
val alias = InterceptorUtils.extractAlias(data.readString()!!)
val keyId = KeyIdentifier(uid, alias)
val params =
keygenParameters[keyId]
?: throw IllegalStateException("No params found for $keyId")
// Generate a software key pair using the new generator.
val keyPair =
CertificateGenerator.generateSoftwareKeyPair(params.toKeyMintAttestation())
?: throw Exception("Failed to generate software key pair.")
generatedKeyPairs[keyId] = keyPair
// Create a successful ExportResult containing the public key.
val exportResultParcel =
Parcel.obtain().apply {
writeInt(KeyStore.NO_ERROR)
writeByteArray(keyPair.public.encoded)
setDataPosition(0)
}
val exportResult = ExportResult.CREATOR.createFromParcel(exportResultParcel)
exportResultParcel.recycle()
callback.onFinished(exportResult)
InterceptorUtils.createSuccessReply()
}
.getOrElse {
SystemLogger.error("[TX_ID: $txId] Failed during handleExportKey.", it)
TransactionResult.ContinueAndSkipPost
}
}
private fun handleAttestKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
return runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val callback =
IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
val alias = InterceptorUtils.extractAlias(data.readString()!!)
val keyId = KeyIdentifier(uid, alias)
// Get the attestation challenge from the arguments.
val params =
keygenParameters[keyId]
?: throw IllegalStateException("No params found for $keyId")
val keyPair =
generatedKeyPairs[keyId]
?: throw IllegalStateException("No keypair found for $keyId")
val attestationArgs = KeymasterArguments()
if (data.readInt() == 1) {
attestationArgs.readFromParcel(data)
val challenge =
attestationArgs.getBytes(
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE,
ByteArray(0),
)
params.attestationChallenge = challenge
}
val certificateChain =
CertificateGenerator.generateCertificateChain(
uid,
keyPair,
null, // No attestKeyAlias in legacy flow
params.toKeyMintAttestation(), // Convert to modern format
1, // SecurityLevel.TRUSTED_ENVIRONMENT
) ?: throw Exception("CertificateGenerator failed to create attested key pair.")
val chainAsByteList = certificateChain.map { it.encoded }
val certChain = KeymasterCertificateChain(chainAsByteList)
callback.onFinished(InterceptorUtils.createSuccessKeystoreResponse(), certChain)
InterceptorUtils.createSuccessReply()
}
.getOrElse {
SystemLogger.error("[TX_ID: $txId] Failed during handleAttestKey.", it)
TransactionResult.ContinueAndSkipPost
}
}
override fun onPostTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): TransactionResult {
if (
target != keystoreService ||
code != GET_TRANSACTION ||
reply == null ||
InterceptorUtils.hasException(reply)
) {
SystemLogger.debug(
"[TX_ID: $txId] Skip parsing post-transaction for [target, code, reply]: [$target, $code, $reply]"
)
return TransactionResult.SkipTransaction
}
if (!ConfigurationManager.shouldPatch(callingUid)) return TransactionResult.SkipTransaction
return try {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val alias = data.readString() ?: ""
val extractedAlias = InterceptorUtils.extractAlias(alias)
val keyId = KeyIdentifier(callingUid, extractedAlias)
SystemLogger.debug(
"[TX_ID: $txId] Parsed $keyId during post-transaction of ${transactionNames[code]}"
)
when {
// Case 1: The app is requesting the leaf certificate.
alias.startsWith(Credentials.USER_CERTIFICATE) -> {
logTransaction(txId, "post-get (user cert)", callingUid, callingPid)
val originalLeafBytes =
reply.createByteArray() ?: return TransactionResult.SkipTransaction
val originalLeafCertResult = CertificateHelper.toCertificate(originalLeafBytes)
if (originalLeafCertResult !is CertificateHelper.OperationResult.Success) {
return TransactionResult.SkipTransaction
}
val originalLeafCert = originalLeafCertResult.data
val tempChain = arrayOf<Certificate>(originalLeafCert)
// Perform the COMPLETE patch and rebuild operation.
val newFullChain =
AttestationPatcher.patchCertificateChain(tempChain, callingUid)
// If patching was successful and we have a valid chain...
if (newFullChain.isNotEmpty() && newFullChain[0] != originalLeafCert) {
// ...cache the entire new chain for the subsequent "ca_cert" call.
patchedChainCache[keyId] = newFullChain
// And return only the new leaf's bytes, as the API expects.
SystemLogger.info(
"[TX_ID: $txId] Patched and cached chain for alias '$extractedAlias'. Returning new leaf."
)
InterceptorUtils.createByteArrayReply(newFullChain[0].encoded)
} else {
// Patching failed or was skipped; do nothing.
TransactionResult.SkipTransaction
}
}
// Case 2: The app is requesting the CA certificate chain.
alias.startsWith(Credentials.CA_CERTIFICATE) -> {
logTransaction(txId, "post-get (ca cert)", callingUid, callingPid)
// Retrieve the full, correct chain we cached during the leaf request.
val cachedChain = patchedChainCache.remove(keyId)
if (cachedChain != null && cachedChain.size > 1) {
// The CA chain is everything *except* the first element (the leaf).
val caCerts = cachedChain.drop(1)
val caCertsBytes = CertificateHelper.certificatesToByteArray(caCerts)
SystemLogger.info(
"[TX_ID: $txId] Returning cached CA chain for alias '$extractedAlias'."
)
InterceptorUtils.createByteArrayReply(caCertsBytes!!)
} else {
SystemLogger.warning(
"[TX_ID: $txId] No cached chain found for CA request on alias '$extractedAlias'. Skipping."
)
TransactionResult.SkipTransaction
}
}
else -> TransactionResult.SkipTransaction
}
} catch (e: Exception) {
SystemLogger.error("[TX_ID: $txId] Failed during legacy post-transaction patching.", e)
TransactionResult.SkipTransaction
}
}
}
/**
* A data class to hold key generation parameters parsed from the legacy IKeystoreService's
* KeymasterArguments. It is used exclusively by the KeystoreInterceptor to manage state during the
* software key generation flow.
*/
private data class LegacyKeygenParameters(
val algorithm: Int,
val keySize: Int,
val purpose: List<Int>,
val digest: List<Int>,
val certificateNotBefore: Date?,
val rsaPublicExponent: BigInteger?,
val ecCurveName: String?, // Derived from keySize
) {
// The challenge is provided in a separate transaction (attestKey), so it must be mutable.
var attestationChallenge: ByteArray? = null
/**
* Converts the legacy parameters into the modern [KeyMintAttestation] data structure, which is
* required by the refactored [AttestationBuilder] and [CertificateGenerator].
*/
fun toKeyMintAttestation(): KeyMintAttestation {
// This conversion acts as a bridge, allowing our new generic components
// to be used by the legacy interceptor.
return KeyMintAttestation(
keySize = this.keySize,
algorithm = this.algorithm,
ecCurve = 0,
ecCurveName = this.ecCurveName ?: "",
origin = null,
blockMode = listOf<Int>(),
padding = listOf<Int>(),
purpose = this.purpose,
digest = this.digest,
rsaPublicExponent = this.rsaPublicExponent,
certificateSerial = null, // Not provided in legacy generateKey
certificateSubject = null, // Not provided in legacy generateKey
certificateNotBefore = this.certificateNotBefore,
certificateNotAfter = null, // Not provided in legacy generateKey
attestationChallenge = this.attestationChallenge,
// Device identifiers are not passed in legacy args;
// AttestationBuilder will fetch them from system properties.
brand = null,
device = null,
product = null,
serial = null,
imei = null,
meid = null,
manufacturer = null,
model = null,
secondImei = null,
)
}
companion object {
/** Factory method to create an instance from a [KeymasterArguments] object. */
fun fromKeymasterArguments(args: KeymasterArguments): LegacyKeygenParameters {
val algorithm = args.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
val keySize = args.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
return LegacyKeygenParameters(
algorithm = algorithm,
keySize = keySize,
purpose = args.getEnums(KeymasterDefs.KM_TAG_PURPOSE),
digest = args.getEnums(KeymasterDefs.KM_TAG_DIGEST),
certificateNotBefore = args.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date()),
rsaPublicExponent =
if (algorithm == KeymasterDefs.KM_ALGORITHM_RSA) getRsaExponent(args) else null,
ecCurveName =
if (algorithm == KeymasterDefs.KM_ALGORITHM_EC) deriveEcCurveName(keySize)
else null,
)
}
private fun deriveEcCurveName(keySize: Int): String =
when (keySize) {
224 -> "secp224r1"
256 -> "secp256r1"
384 -> "secp384r1"
521 -> "secp521r1"
else -> "secp256r1" // Default fallback
}
/**
* The RSA public exponent is not accessible via a public API in KeymasterArguments, so we
* must use reflection to extract it.
*/
private fun getRsaExponent(args: KeymasterArguments): BigInteger? {
return runCatching {
val getArgumentByTag =
KeymasterArguments::class
.java
.getDeclaredMethod("getArgumentByTag", Int::class.java)
getArgumentByTag.isAccessible = true
val rsaArgument =
getArgumentByTag.invoke(args, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
val getLongTagValue =
KeymasterArguments::class
.java
.getDeclaredMethod(
"getLongTagValue",
Class.forName("android.security.keymaster.KeymasterArgument"),
)
getLongTagValue.isAccessible = true
getLongTagValue.invoke(args, rsaArgument) as BigInteger
}
.onFailure {
SystemLogger.error("Failed to read rsaPublicExponent via reflection.", it)
}
.getOrNull()
}
}
}
@@ -1,142 +0,0 @@
package org.matrix.TEESimulator.interception.keystore
import android.os.Parcel
import android.system.keystore2.Domain
import android.system.keystore2.IKeystoreService
import android.system.keystore2.KeyDescriptor
import java.util.TreeMap
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
/**
* Handler to intercept listEntries and listEntriesBatched transactions.
*
* References for all mentioned functions in AOSP:
* https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/database.rs
* https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/service.rs
* https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/utils.rs
*/
object ListEntriesHandler {
// Estimate for maximum size of a Binder response in bytes.
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.
private fun estimateSafeAmountToReturn(
keyDescriptors: Array<KeyDescriptor>,
responseSizeLimit: Int,
): Int {
var itemsToReturn = 0
var returnedBytes = 0
for (kd in keyDescriptors) {
// 4 bytes for the Domain enum
// 8 bytes for the Namespace long
returnedBytes += 4 + 8
kd.alias?.let { returnedBytes += 4 + it.toByteArray(Charsets.UTF_8).size }
kd.blob?.let { returnedBytes += 4 + it.size }
if (returnedBytes > responseSizeLimit) {
SystemLogger.warning(
"Key descriptors list (${keyDescriptors.size} items) may exceed binder size limit, returning $itemsToReturn items with estimated size: $returnedBytes bytes."
)
break
}
itemsToReturn++
}
return itemsToReturn
}
// Parse and store parameters for later use (in post-transaction).
fun cacheParameters(txId: Long, data: Parcel, isBatchMode: Boolean): Boolean {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val domain = data.readInt()
val namespace = data.readLong()
val startPastAlias = if (isBatchMode) data.readString() else null
// List entries is only supported for Domain::APP and Domain::SELINUX.
// See AOSP function `get_key_descriptor_for_lookup` in service.rs.
// Note that all generated keys belong to Domain::APP.
if (domain == Domain.APP) {
pendingParams[txId] = ListEntriesParams(domain, namespace, startPastAlias)
SystemLogger.debug("[TX_ID: $txId] Cached ${pendingParams[txId]}.")
return true
}
return false
}
// Merge software-backed keys with hardware-backed keys in the reply parcel.
fun injectGeneratedKeys(txId: Long, callingUid: Int, reply: Parcel): Array<KeyDescriptor> {
val params =
pendingParams.remove(txId)
?: throw IllegalStateException("No params found for listing entries")
// By default we use the calling uid as namespace if domain is Domain::APP.
// The namespace parameter is thus ignored for non-privileged applications.
// See AOSP function `get_key_descriptor_for_lookup` in service.rs.
val keysToInject =
extractGeneratedKeyDescriptors(callingUid, callingUid.toLong(), params.startPastAlias)
val originalList = reply.createTypedArray(KeyDescriptor.CREATOR)!!
val mergedArray = mergeKeyDescriptors(originalList, keysToInject)
// Limit response size to avoid binder buffer overflow.
// See AOSP function `list_key_entries` in utils.rs.
val safeAmountToReturn = estimateSafeAmountToReturn(mergedArray, RESPONSE_SIZE_LIMIT)
return if (safeAmountToReturn < mergedArray.size) {
SystemLogger.debug(
"[TX_ID: $txId] Listing entries are truncated [${mergedArray.size} -> $safeAmountToReturn] to avoid transaction overflow."
)
mergedArray.copyOfRange(0, safeAmountToReturn)
} else {
SystemLogger.debug(
"[TX_ID: $txId] Listing entries returns ${mergedArray.size} [injected: ${keysToInject.size}] keys."
)
mergedArray
}
}
// Merge hardware and software key descriptors into a single sorted array.
private fun mergeKeyDescriptors(
hardwareKeys: Array<KeyDescriptor>,
keysToInject: List<KeyDescriptor>,
): Array<KeyDescriptor> {
// Uses TreeMap to ensure alphabetical ordering and uniqueness (prefer injected keys).
val combinedMap = TreeMap<String, KeyDescriptor>()
hardwareKeys.forEach { key -> key.alias?.let { combinedMap[it] = key } }
keysToInject.forEach { key -> key.alias?.let { combinedMap[it] = key } }
return combinedMap.values.toTypedArray()
}
// Based on AOSP function `list_past_alias` in database.rs
private fun extractGeneratedKeyDescriptors(
uid: Int,
namespace: Long,
startPastAlias: String?,
): List<KeyDescriptor> {
return KeyMintSecurityLevelInterceptor.generatedKeys.keys
.filter { it.uid == uid && (startPastAlias == null || it.alias > startPastAlias) }
.map { keyId ->
KeyDescriptor().apply {
this.domain = Domain.APP
this.nspace = namespace
this.alias = keyId.alias
this.blob = null
}
}
}
}
@@ -1,378 +0,0 @@
package org.matrix.TEESimulator.interception.keystore.shim
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.security.KeyPair
import java.security.MessageDigest
import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.locks.ReentrantLock
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.CertificateHelper
data class PersistedKeyData(
val uid: Int,
val alias: String,
val nspace: Long,
val securityLevel: Int,
val isAttestationKey: Boolean,
val algorithm: Int,
val keySize: Int,
val ecCurve: Int,
val purposes: List<Int>,
val digests: List<Int>,
val privateKeyBytes: ByteArray,
val certChainBytes: List<ByteArray>,
)
object GeneratedKeyPersistence {
private const val FORMAT_VERSION = 1
private val PERSISTENCE_DIR = File(CONFIG_PATH, "persistent_keys")
// Per-filename locks to prevent concurrent writes to the same key file
private val fileLocks = ConcurrentHashMap<String, ReentrantLock>()
private fun getLockForKey(filename: String): ReentrantLock {
return fileLocks.computeIfAbsent(filename) { ReentrantLock() }
}
fun save(
keyId: KeyIdentifier,
keyPair: KeyPair,
nspace: Long,
securityLevel: Int,
certChain: List<Certificate>,
algorithm: Int,
keySize: Int,
ecCurve: Int,
purposes: List<Int>,
digests: List<Int>,
isAttestationKey: Boolean,
) {
val filename = keyFileName(keyId.uid, keyId.alias)
val lock = getLockForKey(filename)
SystemLogger.debug("[Persistence] Acquiring lock for $filename")
lock.lock()
try {
SystemLogger.debug("[Persistence] Lock acquired for $filename")
runCatching {
PERSISTENCE_DIR.mkdirs()
val finalFile = File(PERSISTENCE_DIR, filename)
val tmpFile = File(PERSISTENCE_DIR, "$filename.tmp")
try {
DataOutputStream(BufferedOutputStream(FileOutputStream(tmpFile))).use { out ->
out.writeInt(FORMAT_VERSION)
out.writeInt(securityLevel)
out.writeInt(keyId.uid)
out.writeUTF(keyId.alias)
out.writeLong(nspace)
out.writeBoolean(isAttestationKey)
out.writeInt(algorithm)
out.writeInt(keySize)
out.writeInt(ecCurve)
out.writeInt(purposes.size)
purposes.forEach { out.writeInt(it) }
out.writeInt(digests.size)
digests.forEach { out.writeInt(it) }
val pkBytes = keyPair.private.encoded
out.writeInt(pkBytes.size)
out.write(pkBytes)
out.writeInt(certChain.size)
certChain.forEach { cert ->
val encoded = cert.encoded
out.writeInt(encoded.size)
out.write(encoded)
}
}
} catch (e: Exception) {
tmpFile.delete()
throw e
}
// Atomic rename — if this fails the tmp is left behind and cleaned on next deleteAll
if (!tmpFile.renameTo(finalFile)) {
tmpFile.delete()
throw IllegalStateException("Failed to atomically rename $tmpFile -> $finalFile")
}
// Verify write succeeded - catches disk-full or filesystem errors
if (!finalFile.exists() || finalFile.length() < 20) {
throw IOException("File write verification failed - possible disk full")
}
SystemLogger.debug("Persisted key: $keyId")
}.onFailure { e ->
SystemLogger.error("Failed to persist key $keyId", e)
}
} finally {
lock.unlock()
SystemLogger.debug("[Persistence] Lock released for $filename")
}
}
fun delete(keyId: KeyIdentifier) {
runCatching {
val file = File(PERSISTENCE_DIR, keyFileName(keyId.uid, keyId.alias))
if (file.exists()) {
if (file.delete()) {
fileLocks.remove(keyFileName(keyId.uid, keyId.alias))
SystemLogger.debug("Deleted persisted key: $keyId")
} else {
SystemLogger.warning("Failed to delete persisted key file: ${file.name}")
}
} else {
SystemLogger.debug("No persisted file to delete for: $keyId")
}
}.onFailure { e ->
SystemLogger.error("Failed to delete persisted key $keyId", e)
}
}
fun deleteAll() {
runCatching {
if (!PERSISTENCE_DIR.exists()) {
SystemLogger.debug("No persistent_keys directory, nothing to delete")
return
}
val files = PERSISTENCE_DIR.listFiles()
if (files == null) {
SystemLogger.warning("Cannot list persistent_keys directory")
return
}
var count = 0
files.forEach { file ->
if (file.name.endsWith(".bin") || file.name.endsWith(".tmp")) {
if (file.delete()) count++
}
}
fileLocks.clear()
SystemLogger.info("Deleted $count persisted key files")
}.onFailure { e ->
SystemLogger.error("Failed to delete all persisted keys", e)
}
}
fun loadAll(securityLevel: Int): List<PersistedKeyData> {
if (!PERSISTENCE_DIR.exists()) {
SystemLogger.debug("No persistent_keys directory, nothing to load")
return emptyList()
}
val files = PERSISTENCE_DIR.listFiles { _, name -> name.endsWith(".bin") }
if (files == null) {
SystemLogger.warning("Cannot read persistent_keys directory")
return emptyList()
}
if (files.isEmpty()) {
SystemLogger.debug("No persisted key files found")
return emptyList()
}
SystemLogger.info("Found ${files.size} persisted key files to process")
val result = mutableListOf<PersistedKeyData>()
for (file in files) {
runCatching {
DataInputStream(BufferedInputStream(FileInputStream(file))).use { input ->
val version = input.readInt()
if (version != FORMAT_VERSION) {
SystemLogger.warning(
"Skipping ${file.name}: unknown format version $version"
)
return@runCatching
}
val storedSecLevel = input.readInt()
val uid = input.readInt()
val alias = input.readUTF()
val nspace = input.readLong()
val isAttestKey = input.readBoolean()
val algo = input.readInt()
val kSize = input.readInt()
val curve = input.readInt()
val purposeCount = requireBounds(input.readInt(), 64, "purposeCount")
val purposes = (0 until purposeCount).map { input.readInt() }
val digestCount = requireBounds(input.readInt(), 64, "digestCount")
val digests = (0 until digestCount).map { input.readInt() }
val pkLen = requireBounds(input.readInt(), 8192, "pkLen")
val pkBytes = ByteArray(pkLen)
input.readFully(pkBytes)
val certCount = requireBounds(input.readInt(), 10, "certCount")
val certChainBytes = (0 until certCount).map {
val certLen = requireBounds(input.readInt(), 65536, "certLen")
val certBytes = ByteArray(certLen)
input.readFully(certBytes)
certBytes
}
if (storedSecLevel == securityLevel) {
result.add(
PersistedKeyData(
uid = uid,
alias = alias,
nspace = nspace,
securityLevel = storedSecLevel,
isAttestationKey = isAttestKey,
algorithm = algo,
keySize = kSize,
ecCurve = curve,
purposes = purposes,
digests = digests,
privateKeyBytes = pkBytes,
certChainBytes = certChainBytes,
)
)
}
}
}.onFailure { e ->
SystemLogger.warning("Skipping corrupted persisted key file: ${file.name}", e)
}
}
SystemLogger.info("Loaded ${result.size} persisted keys for security level $securityLevel")
return result
}
// Re-persist updates the cert chain for an already-persisted key without
// reconstructing authorization parameters from the response. This avoids
// pulling keymint Tag dependencies into this file and is correct because
// the only field that changes post-generation is the patched cert chain.
fun rePersistIfNeeded(
callingUid: Int,
generatedKeyInfo: KeyMintSecurityLevelInterceptor.GeneratedKeyInfo,
) {
val metadata = generatedKeyInfo.response.metadata
if (metadata == null) {
SystemLogger.debug("rePersist: no metadata, skipping")
return
}
val secLevel = metadata.keySecurityLevel
val entry = KeyMintSecurityLevelInterceptor.generatedKeys.entries.find { (id, info) ->
id.uid == callingUid && info.nspace == generatedKeyInfo.nspace
}
if (entry == null) {
SystemLogger.debug("rePersist: key not found in map for uid=$callingUid nspace=${generatedKeyInfo.nspace}")
return
}
val keyId = entry.key
val filename = keyFileName(keyId.uid, keyId.alias)
val existing = File(PERSISTENCE_DIR, filename)
if (!existing.exists()) {
SystemLogger.debug("rePersist: no existing file for $keyId, skipping")
return
}
val newChain = CertificateHelper.getCertificateChain(metadata)
if (newChain == null) {
SystemLogger.warning("rePersist: could not extract cert chain for $keyId")
return
}
val persisted = runCatching {
DataInputStream(BufferedInputStream(FileInputStream(existing))).use { input ->
val version = input.readInt()
if (version != FORMAT_VERSION) {
SystemLogger.warning("rePersist: unknown format version $version for $keyId")
return
}
readPersistedKeyData(input)
}
}.getOrNull()
if (persisted == null) {
SystemLogger.warning("rePersist: failed to read existing data for $keyId")
return
}
save(
keyId = keyId,
keyPair = generatedKeyInfo.keyPair,
nspace = generatedKeyInfo.nspace,
securityLevel = secLevel,
certChain = newChain.toList(),
algorithm = persisted.algorithm,
keySize = persisted.keySize,
ecCurve = persisted.ecCurve,
purposes = persisted.purposes,
digests = persisted.digests,
isAttestationKey = persisted.isAttestationKey,
)
SystemLogger.debug("Re-persisted key $keyId with updated cert chain")
}
// Corrupted binary files can have arbitrary length fields — cap allocations
private fun requireBounds(value: Int, max: Int, name: String): Int {
require(value in 0..max) { "$name out of bounds: $value (max $max)" }
return value
}
private fun keyFileName(uid: Int, alias: String): String {
val digest = MessageDigest.getInstance("SHA-256")
.digest("$uid:$alias".toByteArray(Charsets.UTF_8))
return digest.joinToString("") { "%02x".format(it) } + ".bin"
}
// Reads all fields after version has already been consumed
private fun readPersistedKeyData(input: DataInputStream): PersistedKeyData {
val secLevel = input.readInt()
val uid = input.readInt()
val alias = input.readUTF()
val nspace = input.readLong()
val isAttestKey = input.readBoolean()
val algo = input.readInt()
val kSize = input.readInt()
val curve = input.readInt()
val purposeCount = requireBounds(input.readInt(), 64, "purposeCount")
val purposes = (0 until purposeCount).map { input.readInt() }
val digestCount = requireBounds(input.readInt(), 64, "digestCount")
val digests = (0 until digestCount).map { input.readInt() }
val pkLen = requireBounds(input.readInt(), 8192, "pkLen")
val pkBytes = ByteArray(pkLen)
input.readFully(pkBytes)
val certCount = requireBounds(input.readInt(), 10, "certCount")
val certChainBytes = (0 until certCount).map {
val certLen = requireBounds(input.readInt(), 65536, "certLen")
val certBytes = ByteArray(certLen)
input.readFully(certBytes)
certBytes
}
return PersistedKeyData(
uid = uid,
alias = alias,
nspace = nspace,
securityLevel = secLevel,
isAttestationKey = isAttestKey,
algorithm = algo,
keySize = kSize,
ecCurve = curve,
purposes = purposes,
digests = digests,
privateKeyBytes = pkBytes,
certChainBytes = certChainBytes,
)
}
}

Some files were not shown because too many files have changed in this diff Show More