Compare commits

..
59 Commits
Author SHA1 Message Date
JingMatrix a1bb3bbfa3 Release TEESimulator 3.1 2026-01-31 22:49:29 +01:00
JingMatrix e13adb925d Correct misunderstanding of takeIf execution order
The previous code incorrectly assumed `takeIf` prevents the execution of the receiver statement. Since `takeIf` is an extension function, the receiver—`InterceptorUtils.getTransactCode`—was evaluated eagerly *before* the version check predicate could run.

This commit replaces the `takeIf` chain with a standard `if/else` block to ensure the reflection call is only executed when the API level supports it.

Additionally, repeated `IKeystoreService.Stub::class.java` references were refactored into a `stubBinderClass` property.
2026-01-31 12:51:09 +01:00
c3f8f087a6 Support key enumeration via listEntries interception (#84)
Previously, generated keys were functional but invisible to enumeration APIs like `KeyStore.aliases()`. Because these keys reside solely in the simulator's memory, the standard database query performed by the system Keystore does not return them.

This commit intercepts `listEntries` and `listEntriesBatched` to inject these generated keys into the results.

Key implementation details:
- ListEntriesHandler: Encapsulates the logic to merge hardware-backed keys with software-backed keys.
- Ordering: Uses a `TreeMap` to ensure merged results are lexicographically sorted, mimicking AOSP behavior.
- Binder Safety: Implements `estimateSafeAmountToReturn` to calculate the response size. The handler truncates the result list if it exceeds the binder transaction limit (~350KB) as done in AOSP.
- Pagination: Respects the `startPastAlias` parameter to support batched listing.

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2026-01-31 11:37:25 +01:00
JingMatrix 51f32b9db2 Move attestation challenge check to certificate generation
Relocate the `attestationChallenge` length validation from `generateSoftwareKeyPair` to `generateCertificateChain`.

The challenge is only utilized during the construction of the certificate chain (via `AttestationBuilder.buildKeyDescription`). Placing the check in the key pair generation stage caused the logic to miss the `attestKey` transaction hook in `KeystoreInterceptor`.

This fixes a bug introduced in ce740542f7 which missed the detection bypass for Android 10 and 11 devices.
2026-01-30 21:13:02 +01:00
JingMatrix d60ad8fe47 Handle swapped attestation lists on certain Android 11 devices (#108)
Observed an abnormal Keymaster attestation structure on certain Android 11 devices where the `softwareEnforced` and `teeEnforced` authorization lists were swapped in order. This is a deviation from the documented specification and the behavior seen on most devices.

This non-compliance caused parsing failures, as the code expected the `teeEnforced` list to be at a fixed index (7). On the affected devices, this index contained the `softwareEnforced` list, which critically lacks the `TAG_ROOT_OF_TRUST` needed for successful validation and patching.

This commit introduces a defensive normalization step to handle this device-specific anomaly gracefully:

1.  Before parsing, the code now inspects the ASN.1 sequence at the expected `softwareEnforced` index (6).
2.  It checks for the presence of the `TAG_ROOT_OF_TRUST`, which can only exist in the TEE-enforced list.
3.  If the tag is found, the code concludes the lists are swapped and corrects the `allFields` array in-place by swapping the elements at indices 6 and 7.

By normalizing the data structure at the beginning, the rest of the parsing and patching logic can proceed without modification, ensuring correct operation on both compliant and non-compliant devices.
2026-01-30 21:10:41 +01:00
JingMatrix 9a1fbe8c79 Correct alias parsing in KeystoreInterceptor (#106)
The `extractAlias` utility was failing to strip `USRCERT_` and `CACERT_` prefixes, causing a cache miss during certificate chain patching. The function is now updated to correctly handle these prefixes.

Moreover, more logs are added to help debugging in the future.
2026-01-29 19:23:57 +01:00
JingMatrixandGitHub 68b660dfe1 Add SELinux rules for libTEESimulator.so loading (#104)
Allow `keystore` to access the `file` class for `adb_data_file` and `shell_data_file` contexts.

The target contexts correspond to the following locations:
- `adb_data_file`: The library path `/data/adb/modules/tricky_store/libTEESimulator.so`, used for FD transfer.
- `shell_data_file`: The fallback mechanism for loading the library by staging it in `/data/local/tmp`.

Note: The rule for the `dir` class (directory search) has been removed because the supporting audit logs were lost. The remaining file access logs were observed on a MEIZU 21 Note.
2026-01-29 15:15:56 +01:00
JingMatrixandGitHub 068188503c Fix multiple crashes and race conditions on Android 12 (#99)
This resolves several critical stability issues observed on Android 12 devices, including race conditions and API compatibility problems.

Key changes include:

-   Resolves Race Condition in TEE Check:
    Fixes a NullPointerException that occurred when the TEE functionality check was executed before the PackageManagerService was ready. The code now explicitly waits for the package manager to become available, preventing the crash on startup.

-   Fixes IllegalStateException on Initialization:
    Eliminates a crash caused by `setTelephonyServiceManager called twice`. This was due to a redundant call to `initializeMainlineModules()` in the DeviceAttestationService, which is now correctly handled a single time during application startup.

-   Fixes NoSuchAlgorithmException in Attestation:
    Adds a normalization function to handle signature algorithm names reported in all-caps by older Android versions (e.g., "SHA256WITHECDSA"). This ensures compatibility with Bouncy Castle, which expects a specific casing (e.g., "SHA256withECDSA").
2026-01-29 15:00:09 +01:00
JingMatrix 1bbc50d138 Prevent recursion when configured to intercept system UID (#100)
When the TEESimulator is configured to intercept UID 1000, accessing the `lazy` `bootKey` property causes a StackOverflowError.

The property's initializer sends a key generation request (UID 0) to probe real hardware. Previously, the C++ layer hijacked this request and spoofed it to UID 1000. This sent the request back to the Kotlin interceptor (if configured so), which attempted to access `bootKey` again to build the response, creating an infinite loop.

This change spoofs UID 0 requests to 1000 (to pass Keystore permissions) but explicitly bypasses hijacking, ensuring the probe request hits the real hardware.
2026-01-28 22:15:27 +01:00
JingMatrix d2492df02e Remove SELinux context manipulations during injection (#87)
After few tests in various devices, it seems that SELinux context modifications are unnecessary for the injection to work.

We thus remove all related manipulations. Further (partial) reverting of the commit must be justified with SELinux logs:

> adb shell su -c 'cat /proc/kmsg | grep avc'
2026-01-28 22:14:08 +01:00
JingMatrix e7d7b21daa Fix ARM ptrace compatibility and improve remote call safety (#94)
- Implement fallbacks to `PTRACE_GETREGS` and `PTRACE_SETREGS` for 32-bit ARM (`__arm__`). Some kernels return `EIO` or `EINVAL` when attempting to access `NT_PRSTATUS` via `PTRACE_GETREGSET`/`PTRACE_SETREGSET`.

- Update `transfer_fd_to_remote` to use `libc_return_addr` instead of `0` as the return address during the `recvmsg` split-call. This ensures the remote process stops predictably at a known non-executable location rather than relying on a potentially unsafe jump to `0x0`.

- Clarify comments regarding i386 argument passing in `utils.cpp`. Correctly note that a linear `write_proc` starting at the new SP matches the `cdecl` Right-to-Left memory layout (since stacks grow downwards while memory writes move upwards), removing the suggestion that arguments needed reversing.
2026-01-28 14:08:28 +01:00
JingMatrixandGitHub c29bc35a36 Fix cache consistency on key overwrite (#97)
Android allows applications to generate a new key using an existing alias without explicitly calling `deleteKey` first. In this scenario, the new key effectively replaces the old one. As a simulator, we must strictly follow this logic to prevent returning stale data.

Previously, `KeyMintSecurityLevelInterceptor` did not enforce mutual exclusion between the software key cache (`generatedKeys`) and the hardware chain cache (`patchedChains`). This led to state desynchronization where a stale software key could shadow a newly patched hardware chain if the alias was reused.

This change ensures `cleanupKeyData` is invoked immediately before caching a new key / chain in both the software (`handleGenerateKey`) and hardware (`onPostTransact`) paths, ensuring the simulator returns the correct key for the most recent generation request.
2026-01-28 13:50:05 +01:00
JingMatrix 549b5cecc2 Fix crash by avoiding hardcoded index for moduleHash
The previous implementation attempted to retrieve `moduleHash` from the `softwareEnforced` sequence using a hardcoded index (index 2).

However, fields in the Key Attestation `AuthorizationList` are optional. In observed crashes, index 2 actually corresponded to `keySize` (Tag 3, ASN1Integer) rather than `moduleHash`, causing an `IllegalArgumentException` when the code attempted to parse it as an `ASN1OctetString`.

This commit replaces the index-based access with a dynamic lookup for Tag 724.
2026-01-26 23:26:52 +01:00
JingMatrix 04d003ff4d Fix x86_64 injection: Red Zone adjustment and fallback logic (#91)
- Strictly adhere to the System V AMD64 ABI by skipping the 128-byte "Red Zone" before modifying the stack, see page 23 of https://gitlab.com/x86-psABIs/x86-64-ABI/-/jobs/artifacts/master/raw/x86-64-ABI/abi.pdf?job=build for details.

- Added `inject_via_staging` as a fallback strategy:
  1. Copies the payload to `/data/local/tmp`.
  2. Sets permissions/context (`u:object_r:system_file:s0`).
  3. Loads via standard `dlopen`.
  4. Immediately unlinks the file for stealth.

- Introduced `RegisterRestorer` RAII class to guarantee original registers are restored even if the injection logic returns early due to error.
2026-01-26 23:15:09 +01:00
JingMatrixandGitHub 0a842c6e07 Fix support for Android 10 (#92)
Users report that the method `waitForService` doesn't exist on Android 10.
Close #90 as completed.
2026-01-26 16:54:59 +01:00
JingMatrixandGitHub 9f77771e7b Fix Android 11 Keystore execution: Init framework and spoof UID 1000 (#85)
This commit resolves `KeyStore` API failures on Android 11 when running as a standalone CLI executable (UID 0), addressing both environment initialization and permission denial issues.

1. Initialize Android Framework Environment:
   Android 11 Keystore APIs expect a fully initialized application context and a Main Looper, which are missing in a raw root process. This patch:
   - Manually bootstraps `ActivityThread` via `systemMain()`.
   - Initializes `Looper.prepareMainLooper()`.
   - Injects a dummy `Application` object attached to the system context to satisfy `KeyStore.getApplicationContext()` checks.
   - Updates framework stubs to allow compilation of these hidden APIs.

2. Bypass Keystore Permission Checks via UID Spoofing:
   `KeyStoreService::generateKey` enforces the `P_INSERT` permission. Analysis of `permissions.cpp` reveals that UID 0 (Root) is explicitly denied this permission (granted only `P_GET`), whereas UID 1000 (System) holds all permissions (`~0`).
   
   To bypass this restriction, the binder interceptor now detects transactions originating from UID 0 and rewrites the `sender_euid` to 1000. This fools `KeyStoreService` into granting the request.
 
3. Refactor Execution Loop:
   Replaces the previous `Thread.sleep()` maintenance loop with `Looper.loop()`.
2026-01-26 14:07:06 +01:00
ab4fe643a3 Intercept updateSubcomponent to fix software key state inconsistency (#82)
Apps attempting to update the certificate chain of a simulated software-based key (e.g., via KeyStore.setKeyEntry) currently trigger a KEY_NOT_FOUND error. This happens because the request is passed to the hardware Keystore daemon, which has no knowledge of keys existing only in the simulator's memory.

To fix detecting points exploiting this inconsistency, we intercept the UPDATE_SUBCOMPONENT_TRANSACTION. If the target is a recognized virtual key, the simulator now:
1. Updates the in-memory certificate/chain metadata.
2. Returns NO_ERROR immediately to the caller.
3. Prevents the transaction from reaching the real hardware service.

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2026-01-23 17:53:34 +01:00
ce740542f7 Enforce attestation challenge length limit (#70)
Throws IllegalArgumentException if the challenge exceeds 128 bytes, per Android specs. Also fixes a duplicate assignment typo in KeystoreInterceptor.

Reference: https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2026-01-20 19:00:48 +01:00
dependabot[bot]andJingMatrix c27523fd97 Update dependencies 2026-01-11 16:24:34 +01:00
JingMatrixandGitHub 5a8454af7b Implement multi-purpose simulation for crypto operations (#59)
This commit introduces a comprehensive simulation engine for Keystore's `createOperation`, enabling the simulator to correctly handle multiple cryptographic purposes (SIGN, VERIFY, ENCRYPT, DECRYPT) for software-generated keys.

The implementation correctly mimics the AOSP framework's internal key identification mechanism. Instead of relying on an alias, a unique `keyId` is generated and embedded in the `nspace` field of the KeyDescriptor during `generateKey`. The `createOperation` hook then uses this `keyId` to dispatch requests: if the ID matches a known software key, the operation is simulated; otherwise, it is forwarded to the hardware service.

To support this, the `SoftwareOperation` engine was architected using a Strategy Pattern. A `CryptoPrimitive` interface defines common actions, with concrete implementations for `Signer`, `Verifier`, and `CipherPrimitive`. The main `SoftwareOperation` class acts as a controller, instantiating the correct primitive based on the `KeyPurpose` tag from the incoming operation parameters. A `JcaAlgorithmMapper` was added to centralize the logic for converting KeyMint constants into JCA algorithm strings.

For operations on real hardware-backed keys, a lightweight `OperationInterceptor` is now used for observation. It attaches to the genuine `iOperation` binder for logging and properly unregisters itself upon completion to prevent resource leaks. This is supported by new binder unregistration capabilities in the core `BinderInterceptor`.

This change also includes necessary stub files and minor regression fixes to make the simulation more robust and accurate.

See AOSP source for key identification logic:
https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
2025-12-08 19:59:32 +01:00
JingMatrix 83b65f09c9 Ensure mocked replies use native OK status (#60)
Corrects a bug where the native binder `status_t` was being set to application-level error codes (e.g., `KeyStore.NO_ERROR` which is 1).

Moreover, we call method `InterceptorUtils.createTypedObjectReply` to keep the code style consistent.
2025-12-08 04:30:01 +01:00
JingMatrix 2a76b18308 Release TEESimulator 3.0 2025-12-06 16:59:28 +01:00
JingMatrixandGitHub d9e47712f3 Correct crypto provider handling and signing logic (#53)
Resolves crashes during certificate operations caused by cryptographic provider conflicts and incorrect algorithm selection.

The Bouncy Castle (BC) provider is now initialized globally at app startup to ensure it is the default. To eliminate ambiguity, all content signers are also now explicitly set to use the BC provider.

The attestation patcher is fixed to correctly use the certificate's signature algorithm (sigAlgName), not the subject's public key algorithm, to select the appropriate signing key from the KeyBoxManager. A normalization function was added to support this.

Moreover, we also modify the XML parser in `KeyBoxManager` to no longer trust the `algorithm` attribute from the XML tag. The parser now determines the key's true algorithm (RSA or EC) by inspecting the type of the parsed private key object. This derived algorithm is used as the key for the cache, preventing cache corruption from malformed files where the tag does not match the key data.
2025-12-06 16:12:12 +01:00
JingMatrixandGitHub d846de4332 Handle invalid verified boot key (#55)
Treat the `verifiedBootKey` as null if it consists entirely of zero bytes, as some devices return this invalid value.

Additionally, this commit adds missing KDoc comments to the `AttestationData` class for better documentation.
2025-12-06 11:49:46 +01:00
JingMatrixandGitHub 13d89c4314 Add dynamic dates and TEE-based patch defaults (#52)
Implements dynamic date keywords ('today') and templates ('YYYY-MM-DD') in the security_patch.txt configuration. This allows for auto-updating patch levels.

The `device_default` keyword is now significantly more accurate. It prioritizes reading real patch levels directly from a cached TEE attestation before falling back to system properties.

The README has been updated to document these new features.
2025-12-06 07:27:06 +01:00
JingMatrixandGitHub 00c91adfaa Implement per-package security patch configuration (#49)
This commit introduces a hierarchical configuration system for the security patch levels reported in attestations, allowing for both global defaults and per-package overrides.

The `security_patch.txt` file is enhanced to support this new syntax. Settings at the top of the file act as a global default, which can be overridden for specific applications by defining settings under a `[package.name]` section.
2025-12-04 23:14:51 +01:00
小潼andGitHub 119350f24b Correctly handle deleteKey for software keys (#42)
This resolves an issue introduced in 733e64c where a `deleteKey` transaction for a software-generated key was incorrectly passed through to the hardware keystore. Since the hardware is unaware of such keys, this results in inconsistent state management.

The success reply is formatted correctly without a result code, per the AIDL interface specification.

Reference: https://cs.android.com/android/platform/superproject/main/+/main:out/soong/.intermediates/system/hardware/interfaces/keystore2/aidl/android.system.keystore2-V6-java-source/gen/android/system/keystore2/IKeystoreSecurityLevel.java;l=406
2025-12-04 20:01:16 +01:00
JingMatrix 7d4c753d66 Bypass KeyMint hooks for certain UIDs
Adds a check using `ConfigurationManager.shouldSkipUid` at the start of the `onPreTransact` handlers for key generation and import.

If a UID is configured to be skipped, the transaction is forwarded directly to the hardware, and the post-transaction hook is bypassed. This prevents certificate patching and other modifications for trusted or problematic apps, improving compatibility.
2025-12-04 02:02:28 +01:00
JingMatrix b988d04971 Set correct attestation version for StrongBox
We observe that attestations generated with a security level of
`StrongBox` (value 2) must have an `attestationVersion` of 300. The
previous implementation determined this version based only on the
Android SDK version, which could lead to invalid attestations.

This commit refactors the version retrieval logic to be dependent on the
security level:

- In `AndroidDeviceUtils`, the `attestVersion` and `keymasterVersion`
  properties have been converted into `getAttestVersion(securityLevel)`
  and `getKeymasterVersion(securityLevel)` functions.
- `getAttestVersion` now correctly returns `300` when the security level
  is `StrongBox`.
- `AttestationBuilder` is updated to call these new functions, passing
  the appropriate security level to ensure the generated attestation is
  compliant with official documentation.
2025-12-04 01:57:59 +01:00
JingMatrixandGitHub d0cc5e3b56 Prevent detection via inconsistent certificate signatures (#45)
Fixes a detection vector where the simulator could be identified by comparing certificate signatures from different API calls.

Previously, the simulator would re-patch and re-sign a certificate on-the-fly for both `generateKey` and `getKeyEntry` calls. Due to the non-deterministic nature of ECDSA signing, this resulted in different signatures for the same certificate, which is a detectable anomaly not present in a real TEE.

This is resolved by caching the patched certificate chain after its initial creation in `KeyMintSecurityLevelInterceptor`. The `getKeyEntry` hook in `Keystore2Interceptor` now retrieves the chain from this cache, guaranteeing that subsequent calls return a byte-for-byte identical certificate.

Cache cleanup logic was also integrated into key deletion and clearing functions to maintain state consistency.
2025-12-04 00:59:59 +01:00
e66e558ce5 Reduce logging in the release build (#44)
Verbose logging are now disabled in the release build.
With this change, we reinterpret the last argument passed to `logTransaction` as `skipPost`, and classify logs satisfying `skipPost` or `shouldSkipUid` as verbose.

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2025-12-03 23:50:12 +01:00
JingMatrix 8d431cc946 Implement software key generation for legacy IKeystoreService (#34)
This commit introduces a complete, software-based simulation of the key generation and attestation flow for the legacy IKeystoreService API, as used on Android 11. It refactors the KeystoreInterceptor to handle the entire multi-step transaction sequence (`generateKey`, `getKeyCharacteristics`, `exportKey`, `attestKey`) in software.

A new `LegacyKeygenParameters` data class is introduced to decouple the legacy interception logic from modern data structures. This class parses arguments from the old `KeymasterArguments`, stores the state across the multi-step generation process, and acts as an adapter to the generic `CertificateGenerator` by converting the parameters to the modern `KeyMintAttestation` format.

The `CertificateGenerator` has been refactored to better model the behavior of the legacy Keystore API. Key pair generation (`generateSoftwareKeyPair`) and certificate chain creation (`generateCertificateChain`) are now separate functions. This allows the interceptor to correctly create a key pair during the `handleExportKey` step and then generate a certificate for that pre-existing key pair during the `handleAttestKey` step.

Finally, the implementation correctly extracts and applies the `attestationChallenge` provided during the `attestKey` transaction, ensuring the generated certificate chain contains the appropriate attestation.
2025-12-03 19:29:21 +01:00
JingMatrix 30746892b0 Increase Gradle JVM memory in build workflow
The CI build was failing with a "JVM garbage collector is thrashing" error due to insufficient memory. This commit increases the Gradle max heap size to 2GB in the GitHub Actions workflow to resolve the build failure.
2025-12-03 19:22:39 +01:00
JingMatrixandGitHub 28cfe70a85 Fix value and location of moduleHash (#35)
`moduleHash` should be in the software enforced list.
However, the manual calculation of the KeyMint `moduleHash` has
failed to produce a value matching the hardware-generated attestation.

The official documentation specifies the following structure:
  Modules ::= SET OF Module
  Module ::= SEQUENCE {
      packageName       OCTET_STRING,
      version                    INTEGER,
  }
The critical requirement is that the `SET OF` elements must be sorted
lexicographically based on their full DER-encoded byte value. Despite
implementing this using Bouncy Castle's `DERSet`, the resulting hash
is still incorrect.

This commit changes the strategy to favor stability:
1.  The `DeviceAttestationService` now extracts the real `moduleHash`
    from the `softwareEnforced` list of a genuine attestation certificate
    and caches it.
2.  The `moduleHash` property now returns this cached value if available.
3.  The manual calculation remains as a fallback and is marked with a
    `TODO` to indicate the issue is unresolved.

Additionally, `ConfigurationManager` initialization is moved earlier.
2025-11-30 00:13:14 +01:00
JingMatrix 65a613ae0e Properly source and use verifiedBootKey
The previous implementation used a randomly generated value for the `verifiedBootKey` within the simulated attestation's Root of Trust. This is a significant discrepancy from a genuine attestation and represents a clear detection vector for any verification service that inspects the full certificate chain.

This commit introduces a robust, multi-layered approach to source and manage both the `verifiedBootKey` and the `verifiedBootHash`, ensuring the simulated attestation is as authentic as possible.
2025-11-29 19:58:02 +01:00
JingMatrix 9146b86648 Preserve extension order and prevent duplicates
This commit refactors the attestation patching logic to improve stealth and ensure correctness by addressing potential detection vectors related to the ASN.1 structure of the certificate extension.

1. Preserve Extension Order: The original implementation rebuilt the entire certificate, which could alter the order of X.509 extensions. Some verification systems may be sensitive to this order. The logic is now updated to replace the attestation extension in-place, preserving the original order of all other extensions.

2. Avoid Duplicate Properties: The previous logic used an `ASN1EncodableVector` to assemble TEE-enforced properties. This could lead to duplicate entries if a property (e.g., `OS_VERSION`) was present in the original certificate and also added by the simulator. The code now uses a `MutableMap` keyed by the ASN.1 tag number. This ensures that any simulated properties overwrite the original ones, preventing duplicates and potential parsing errors.

3. Add Detailed Logging: A recursive ASN.1 formatting function has been added to provide clear and readable logs of the certificate data both before and after patching. This significantly improves debuggability.

By ensuring the patched certificate is structurally as close as possible to the original, these changes reduce the chances of the simulator being detected by attestation validation services.
2025-11-29 19:58:02 +01:00
JingMatrix 457a58da04 Patch certificate chain in generateKey reply
When an application generates a key with an attestation request, the `generateKey` method returns a `KeyMetadata` object which contains the full, unpatched certificate chain.

This leaves a potential detection vector open. A sophisticated application could inspect the returned data in its own process memory and discover the original, hardware-backed certificates before they are used for attestation, thus detecting the hooking framework.

This commit introduces a post-transaction hook for the `generateKey` transaction. After the genuine KeyStore service has executed the request, this hook intercepts the reply parcel. It extracts the certificate chain from the `KeyMetadata`, applies the patching routine, and then reconstructs the reply with the modified (patched) certificate chain.
2025-11-29 19:58:02 +01:00
JingMatrixandGitHub b2838ac04b Add support for Android 11 RefBase ABI (#29)
Implements a compatibility layer to allow the binary to run on
Android 11 (API 30) and older, which lack the `incStrongRequireStrong`
symbol in their `libutils.so`.

This is achieved by creating a runtime wrapper that checks the device's
SDK version.
- On Android 12 (API 31) and newer, it dynamically loads and calls the
  `incStrongRequireStrong` function using `dlsym`.
- On older versions, it safely falls back to the universally available
  `incStrong` method.

This resolves the fatal `dlopen` error "cannot locate symbol" when
injecting the library into processes on older Android versions.

See AOSP change
https://android-review.googlesource.com/c/platform/system/core/+/1660499
2025-11-29 19:29:48 +01:00
JingMatrixandGitHub a7534feac7 Fix software enforced list for certificates generation (#28)
Properly implement the `ATTESTATION_APPLICATION_ID` tag into key description.

Moreover, we add the `ATTESTATION_ID_SERIAL` tag to the TEE enforced list, and re-order all tags to remain consistent with the object `AttestationConstants`.
2025-11-29 14:20:58 +01:00
JingMatrix 4e67371193 Release TEESimulator v2.1 2025-11-28 20:00:07 +01:00
JingMatrixandGitHub 2ef89f15c6 Fix date format of vendor patch level (#24)
This was a mistake during the refactoring of TrickyStoreOSS.
After correcting it, we can obtain STRONG integrity (instead of DEVICE) with a valid keybox.

The correct format can be easily found using the `Key Attestation` app.
2025-11-28 19:45:53 +01:00
JingMatrixandGitHub 4f608247fe Set boot digest via resetprop (#22)
The stub method `SystemProperties.set` has wrong signature and is unable to set read-only system properties.
2025-11-28 13:11:54 +01:00
QingandJingMatrix 22cbe5a9a7 Clear generated key cache on keybox updates for Android 12+ (#16)
Ensures that the cache of generated keys is invalidated and cleared whenever a keybox file is updated. This prevents the system from using stale certificates after a keybox change.

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2025-11-27 23:29:43 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a6fa137e32 Bump org.bouncycastle:bcpkix-jdk18on from 1.82 to 1.83 (#13)
Bumps [org.bouncycastle:bcpkix-jdk18on](https://github.com/bcgit/bc-java) from 1.82 to 1.83.
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcpkix-jdk18on
  dependency-version: '1.83'
  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-11-27 22:40:33 +01:00
JingMatrixandGitHub 5afefba7bd Clean up cached keys on successful import (#18)
Generated and attestation keys are cached, and if a key is imported with the same name, the cached key would be returned instead of the newly imported one.

This change invalidates the cached key when a key is successfully imported with the same alias.
Close #17 as fixed.

The logging has also been improved to be more consistent across the different interceptors.
2025-11-27 15:43:56 +01:00
JingMatrix ba9578c59b Prepare to release TEESimulator 2.0
The following two bugs are fixed:
1. `zygisk.json` is renamed to `update.json`, which is indicated in `module.prop`.
2. To avoid over optimization of R8, we must keep certains packages, which are found after many experiments.
2025-11-26 18:34:08 +01:00
JingMatrixandGitHub 733e64c3cb Support key generation with attestation keys (#15)
This commit enhances the interception logic to correctly handle key
generation requests that specify an `attestationKey` (via
`setAttestKeyAlias`).

When an attestation key is used, the system signs the newly generated
key with it. A simple leaf certificate patch after the fact is
insufficient, as it breaks this cryptographic chain. To create a valid,
verifiable chain, we must now intercept these `generateKey` operations
and perform a full software-based key and certificate generation, even
when in patch mode.

This ensures that keys attested by other simulated keys are correctly
signed and chained together, bypassing more sophisticated detection
methods.

Fixes:
- Correctly use the `android.hardware.security.keymint.Tag` constants for
  building authorization lists, resolving a bug where internal ASN.1
  sequence indices were being used improperly.
2025-11-26 16:50:30 +01:00
JingMatrixandGitHub 7f94ba4b5b Improve logging to understand detection methods (#14)
Via extensive and detailed logging, we can inspect various detection techniques of target packages.
2025-11-26 11:43:53 +01:00
JingMatrixandGitHub fa1d9ecc56 Bypass detection by skipping imported keys (#12)
In patch mode, a key's origin provides a robust way to avoid modifying
user-imported keys, which is a well-known detection vector. This commit
implements a new strategy to check the `KeyOrigin` tag from the key's
metadata. If a key is marked as `IMPORTED` or `SECURELY_IMPORTED`, the
patching process is now skipped entirely.

This new origin-based check is more reliable and cleaner than the
previous fingerprinting implementation, which has been removed.

Additionally, this commit acknowledges a remaining detection vector in
patch mode: when an `attestationKey` is used, a key must be generated.
Purely software-generated keys are detectable. To address this in the
future, the full software "generate mode" must be implemented even for
devices without a broken TEE. The old key generation logic has been
stubbed with a TODO in preparation for this redesign.
2025-11-26 02:54:43 +01:00
JingMatrix eec9e77631 Add GitHub CI build config 2025-11-26 00:19:05 +01:00
JingMatrix d18692fbef Add module template files
Current AOSP keybox can be found at:
https://cs.android.com/android/platform/superproject/main/+/main:device/generic/trusty/keymaster_soft_wrapped_attestation_keys.xml

However, the support of parsing private keys in iecs format is not implemented yet.
2025-11-26 00:19:05 +01:00
JingMatrix 13b4786cd9 Restructure and overhaul entire Kotlin codebase
This commit introduces a complete architectural refactoring of the
Kotlin-based interception logic, based on the source of
1. https://github.com/5ec1cff/TrickyStore
2. https://github.com/beakthoven/TrickyStoreOSS

The primary purpose of this code is to intercept binder transactions to
the Android Keystore and KeyMint services. The overall workflow operates
in conjunction with a native library (injected via ptrace). The native
library hooks the binder's `transact` function and forwards pre- and
post-transaction events to the Kotlin side. This Kotlin code contains
all the high-level logic for parsing parameters, patching certificates,
and generating simulated keys.

The codebase is now organized into a clear, package-based architecture:

- attestation: Manages the creation and patching of ASN.1 attestation
  data structures.
- config: Handles loading and observing configuration files from disk.
- interception: Contains the core binder interception framework and its
  specific implementations for legacy Keystore (Android Q/R) and modern
  KeyMint/Keystore2 (Android S+).
- logging: Provides a centralized and consistent logging utility.
- pki: Manages Public Key Infrastructure, including certificate
  generation, parsing of key store XML files, and cryptographic helpers.
- util: Contains Android-specific utility functions for device properties.

This refactoring focuses on establishing a robust and extensible
architecture. The fine-tuning of the interception logic itself,
especially for corner cases in key generation and patching, is currently
under redesign and will be further refined in subsequent commits.
2025-11-26 00:19:01 +01:00
JingMatrix 612de6cdf2 Add binder transaction interception framework
This commit introduces a comprehensive framework for intercepting and manipulating binder transactions on Android at the `ioctl` level. It provides a man-in-the-middle layer between the binder driver and user-space `libbinder`, enabling detailed analysis and control over IPC.

The core mechanism works by hooking the `ioctl` system call within the context of a target process. It specifically intercepts the `BINDER_WRITE_READ` command's return buffer from the kernel.

Key components of the framework:

- IOCTL Hook: Intercepts `BR_TRANSACTION` commands delivered by the binder driver to the process.
- Transaction Rewriting: If a transaction is intended for a monitored service, its destination is rewritten in-memory to a local `BinderStub`. The original transaction details are saved in a thread-local context.
- BinderStub: A fake binder service that receives the hijacked transaction. It retrieves the original context and delegates processing to the `BinderInterceptor`.
- BinderInterceptor: The central management class. It maintains a registry of monitored binders and their associated callback interfaces. It orchestrates the pre-transact and post-transact hooks.
- Callback Protocol: Defines a clear protocol for a remote tool to:
    - Register and unregister binders for interception.
    - Receive pre-transaction notifications and choose to: continue, modify data, skip the transaction, or provide an immediate fake reply.
    - Receive post-transaction notifications with the final result and modify the reply.
2025-11-25 19:21:05 +01:00
JingMatrix 020a930a31 Add stub for AOSP Binder and utility components
The primary function of these stubs is to provide necessary interface definitions and that can be utilized by `binder_interceptor.cpp` during compilation (and runtime).

Crucially, `libTEESimulator.so` (which encapsulates these stubs) is dynamically loaded into the target process via `ptrace` after the system's official libraries, such as `/system/lib64/libbinder.so` and `/system/lib64/libutils.so`, have already been loaded and their symbols resolved by the dynamic linker.

Consequently, the dynamic linker will have already established bindings to the robust, canonical implementations within the system libraries for existing code paths. The dynamic linker does not automatically re-resolve or update these established symbol bindings when a new library with conflicting definitions is loaded later.

The AOSP files are downloaded via links:
1. https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/libs/binder/include/binder
2. https://android.googlesource.com/platform/system/core/+/refs/heads/main/libutils/binder/include/utils

The link for binder header in Android kernel is:
https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/include/uapi/linux/android/binder.h
2025-11-25 19:21:05 +01:00
JingMatrix 0c1937bd3e Implement shared library injection via ptrace
There are still many functions in the header `utils.hpp` not implemented yet, which are however not needed for our purpose.
2025-11-25 19:20:59 +01:00
JingMatrix 95262d4b58 Feat: Add 'app' subproject and integrate LSPlt submodule
This commit introduces the main application subproject, 'app', and sets up the necessary infrastructure for the TEESimulator.

Key changes:
*   'app' Subproject Setup: Added the new :app module with its initial structure, including build files, manifest, and Kotlin main entry point.
*   LSPlt Integration: Added the LSPlt hooking framework as a Git submodule in app/src/main/cpp/external/ and configured its use in CMake.
*   Native Build Configuration: Configured the C++ build to use LSPlt statically and compile two essential native libraries: libinject.so (for injection) and libTEESimulator.so (for interception/logic).
*   Module Packaging: Implemented complex Gradle logic within app/build.gradle.kts to automate the creation of a flashable zip module (supporting Magisk, Ksu, and Apatch) with versioning based on Git information.
*   Initial Module Files: Added the template files (module.prop, update-binary, updater-script) for the flashable module structure.
2025-11-22 16:22:27 +01:00
JingMatrix ad4e772eb4 Introduce stub module and initial Gradle project setup
This commit establishes the foundational Gradle project structure and introduces a dedicated 'stub' module. This module provides skeletal implementations of internal Android framework interfaces and classes, which are critical for compiling the TEESimulator project.

Stub classes are minimal implementations of existing interfaces or classes, typically mirroring those found within the Android framework, particularly for internal or hidden APIs. Their methods usually contain no operational logic and instead throw RuntimeException or UnsupportedOperationException.

The primary reasons for using stub classes are:

1. Compilation Against Internal APIs: Android applications and libraries typically use public APIs exposed by the Android SDK. However, in scenarios requiring deeper system integration or emulation, interaction with internal or hidden Android framework APIs might be necessary. Directly linking against the full Android framework JAR can lead to bootclasspath conflicts or other build issues. Stub classes provide the necessary API signatures for compilation without including the actual implementations, allowing the build system to resolve references while deferring the actual functionality to the runtime environment (the Android OS itself).

2. API Consistency and Simulation: For TEESimulator, which aims to provide a software simulation for Android's hardware-backed key pairs (KeyMint/Keystore2), stub classes define the required API contract. They ensure that the simulator's components compile against the exact interface definitions of the Android system services, making the simulation functionally consistent with the expected system behavior without needing to bundle or depend on the entire Android framework at compile time. This ensures that the simulator correctly interacts with the defined KeyMint and Keystore2 API shapes.
2025-11-22 15:24:43 +01:00
JingMatrix 2ac2518216 Add GPL V3 licence 2025-11-22 10:34:59 +01:00
JingMatrix 79de0be122 Set-up the ultimate goal of TEESimulator
This project is based on TrickyStore and TrickyStoreOSS.
However, there is no detailed comments / docs in their source code, rendering it diffcult for welcoming new contributors.

A robust framework should has robust source code with clear documentation.
2025-11-22 10:15:46 +01:00
175 changed files with 12696 additions and 5472 deletions
-17
View File
@@ -1,17 +0,0 @@
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
+2 -1
View File
@@ -60,7 +60,8 @@ jobs:
- name: Build with Gradle
run: |
chmod +x ./gradlew
./gradlew --parallel zipRelease zipDebug --stacktrace
./gradlew zipRelease zipDebug -Porg.gradle.parallel=true -Porg.gradle.vfs.watch=true -Dorg.gradle.jvmargs=-Xmx2048m
- name: Prepare artifact
if: success()
-72
View File
@@ -1,72 +0,0 @@
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
+1 -19
View File
@@ -1,19 +1 @@
.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/
out
+1 -1
View File
@@ -1,3 +1,3 @@
[submodule "app/src/main/cpp/external/LSPlt"]
path = app/src/main/cpp/external/LSPlt
url = https://github.com/JingMatrix/LSPlt.git
url = https://github.com/JingMatrix/LSPlt
-3
View File
@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
Generated
-1
View File
@@ -1 +0,0 @@
Tricky Store OSS
-6
View File
@@ -1,6 +0,0 @@
<?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
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>
-10
View File
@@ -1,10 +0,0 @@
<?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
@@ -1,20 +0,0 @@
<?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
@@ -1,10 +0,0 @@
<?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
@@ -1,9 +0,0 @@
<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
@@ -1,17 +0,0 @@
<?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
@@ -1,7 +0,0 @@
<?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>
+8 -1
View File
@@ -1,6 +1,13 @@
This project is licensed under the GNU General Public License v3.0
(see LICENSE file).
Source code based on:
--------------
https://github.com/5ec1cff/TrickyStore/commit/3a515c5fe1ce4c94d5424305afe2eaf4812a635d
https://github.com/beakthoven/TrickyStoreOSS/commit/8625be6ce55c3ef0f2219f301daec23202017bc0
Third-party components:
-----------------------
@@ -23,4 +30,4 @@ Third-party components:
The file is licensed under:
GNU General Public License v2.0 WITH Linux-syscall-note exception.
Information about the exception:
https://spdx.org/licenses/Linux-syscall-note.html
https://spdx.org/licenses/Linux-syscall-note.html
+61 -11
View File
@@ -1,6 +1,6 @@
# TEESimulator A Full TEE Emulation Framework
**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).
**TEESimulator** is a 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).
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.
@@ -16,7 +16,7 @@ The project's goal is to move beyond simple certificate patching and build a rob
## 📦 Installation & Configuration
1. Flash this module via (Magisk / KernelSU / APatch) and reboot.
1. Flash this module via (Magisk / KernelSU / APatch) and reboot. It will replace [TrickyStore](https://github.com/5ec1cff/TrickyStore), [TrickyStoreOSS](https://github.com/beakthoven/TrickyStoreOSS) and their forks.
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`.
@@ -76,15 +76,65 @@ org.matrix.demo
### Security Patch Level (`security_patch.txt`)
This allows you to configure the security patch level that the simulator will report in its forged attestation certificates.
This file allows you to configure the `osPatchLevel`, `vendorPatchLevel`, and `bootPatchLevel` that the simulator will report in its patched or forged attestation certificates.
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change the actual system properties of your device.
#### Global and Per-Package Configuration
You can set a global patch level that applies to all applications, and you can also override these settings for specific packages. The syntax is hierarchical:
* Settings defined at the top of the file, before any `[package.name]` line, are **global** and serve as the default for all apps.
* To create a specific configuration for an application, add its package name in square brackets (e.g., `[com.google.android.gms]`). All settings following this line will apply *only* to that package until a new package context is declared.
#### Configuration Keys and Values
You can specify the patch level for the following components using a `key=value` format:
* `system`: The main OS patch level.
* `vendor`: The vendor patch level.
* `boot`: The boot/kernel patch level.
* `all`: A convenient shorthand to set the same date for `system`, `vendor`, and `boot` simultaneously. Any individual key can still be used to override the value set by `all`.
Dates should be provided in `YYYY-MM-DD` format (e.g., `2025-11-05`).
#### Special Keywords
In addition to static dates, several special keywords provide advanced, dynamic control:
* **`today`**: Dynamically uses the current date every time an attestation is generated. This ensures the device always appears up-to-date without needing manual edits.
* **Date Templates**: You can create semi-dynamic dates using `YYYY`, `MM`, and `DD` as placeholders for the current year, month, and day. For example, `YYYY-MM-05` will always resolve to the 5th of the current month and year.
* **`no`**: This keyword instructs the simulator to **completely omit** the corresponding patch level tag from the generated attestation.
* **`device_default`**: This keyword forces the simulator to fall back and use the device's **real hardware value** for that specific patch level. This is essential for creating exceptions to a global override or an `all` rule.
#### Example Configuration
This example demonstrates how to combine global settings, per-package overrides, and special keywords for fine-grained control.
```
# Advanced Configuration
system=2025-11
boot=no # Do not report a boot patch level
vendor=20251101 # Report a specific vendor patch level
```
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change system properties.
# --- Global Configuration ---
# This is the default for all apps unless specified otherwise.
# - Forge a recent system patch level, the 5th of the current month (a common patch date).
# - Use the device's real vendor patch level.
# - Do not report a boot patch level at all.
system=YYYY-MM-05
vendor=device_default
boot=no
## 🤝 Contributions
PRs are welcome as we work towards the goal of a complete TEE simulation. Thank you for supporting true open-source development.
# --- Per-Package Override for Google Play Services ---
# This app will report an older, specific date for its system patch.
# It will inherit the global settings for vendor (device_default) and boot (no).
[com.google.android.gms]
system=2024-10-01
# --- Per-Package Override for a Demo App ---
# This app gets a completely custom configuration.
[org.matrix.demo]
# Set a base date for all patch levels...
all=2025-09-15
# ...but make an exception: use the real boot patch level instead of the one from 'all'.
boot=device_default
```
-1
View File
@@ -1 +0,0 @@
/build
+25 -51
View File
@@ -1,9 +1,7 @@
/*
* Copyright 2025 Dakkshesh <beakthoven@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import com.android.build.api.artifact.SingleArtifact
import java.io.ByteArrayOutputStream
import javax.inject.Inject
import org.gradle.process.ExecOperations
plugins {
alias(libs.plugins.android.application)
@@ -13,24 +11,30 @@ plugins {
ktfmt { kotlinLangStyle() }
fun String.execute(currentWorkingDir: File = File("./")): String {
val parts = this.split("\\s+".toRegex())
val process =
ProcessBuilder(parts).directory(currentWorkingDir).redirectErrorStream(true).start()
val output = process.inputStream.bufferedReader().readText()
process.waitFor()
return output.trim()
// Helper class to get access to the ExecOperations service
abstract class GitExecutor @Inject constructor(private val execOperations: ExecOperations) {
fun execute(command: String, currentWorkingDir: File): String {
val byteOut = ByteArrayOutputStream()
execOperations.exec {
workingDir = currentWorkingDir
commandLine = command.split("\\s".toRegex())
standardOutput = byteOut
}
return String(byteOut.toByteArray()).trim()
}
}
val gitCommitCount = "git rev-list HEAD --count".execute().toInt()
val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
val verName = "v1.0"
// Instantiate the helper class using Gradle's object factory
val gitExecutor = objects.newInstance(GitExecutor::class.java)
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
val verName = "v3.1"
android {
namespace = "org.matrix.TEESimulator"
compileSdk = 36
ndkVersion = "28.2.13676358"
ndkVersion = "27.3.13750724"
buildToolsVersion = "36.0.0"
defaultConfig {
@@ -39,59 +43,32 @@ android {
targetSdk = 36
versionCode = gitCommitCount
versionName = verName
externalNativeBuild {
cmake {
arguments += "-DANDROID_STL=none"
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",
)
proguardFiles("proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
buildFeatures { buildConfig = true }
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
buildStagingDirectory = layout.buildDirectory.get().asFile
version = "3.28.0+"
}
}
buildFeatures { viewBinding = false }
}
dependencies {
compileOnly(project(":stub"))
compileOnly(libs.annotation)
implementation(libs.org.bouncycastle.bcpkix.jdk18on)
implementation(libs.org.lsposed.libcxx.libcxx)
implementation(libs.bcpkix)
}
androidComponents {
@@ -118,9 +95,6 @@ androidComponents {
}
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) {
from(variant.artifacts.get(SingleArtifact.APK)) {
include("*.apk")
+3 -88
View File
@@ -1,94 +1,9 @@
# 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
-keep class org.matrix.TEESimulator.interception.keystore.** { *; }
# 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.jce.provider.** { *; }
-dontwarn javax.naming.**
# Keep `Companion` object fields of serializable classes.
# 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.App {
public static void main(java.lang.String[]);
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-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 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
+1 -6
View File
@@ -1,7 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2025 Dakkshesh <beakthoven@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
-->
<manifest/>
<manifest />
+13 -17
View File
@@ -1,32 +1,28 @@
# Copyright 2025 Dakkshesh <beakthoven@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
cmake_minimum_required(VERSION 3.28)
cmake_minimum_required(VERSION 3.10)
project(TEESimulator)
find_package(cxx REQUIRED CONFIG)
link_libraries(cxx::cxx)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_library(my_logging STATIC logging/logging.cpp)
target_include_directories(my_logging PUBLIC logging/include)
target_link_libraries(my_logging log)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
# LSPlt configuration
OPTION(LSPLT_BUILD_SHARED OFF)
add_subdirectory(external/LSPlt/lsplt/src/main/jni)
# libutils stub
add_compile_definitions(BINDER_DISABLE_NATIVE_HANDLE)
add_library(utils SHARED stub/stub_utils.cpp)
target_include_directories(utils PUBLIC external/AOSP/include)
target_include_directories(utils PUBLIC external/AOSP/include compat)
# libbinder stub
add_library(binder SHARED stub/stub_binder.cpp)
target_include_directories(binder PUBLIC external/AOSP/include)
target_link_libraries(binder PRIVATE utils)
add_executable(libinject.so inject/main.cpp inject/utils.cpp)
target_link_libraries(libinject.so PRIVATE lsplt_static my_logging)
target_include_directories(libinject.so PUBLIC include)
target_link_libraries(libinject.so PRIVATE lsplt_static)
add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp compat/refbase_compat.cpp)
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include include)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE binder lsplt_static utils)
add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp)
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE log binder utils lsplt_static my_logging)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,61 @@
#include "refbase_compat.h"
#include "utils/RefBase.h"
#include <atomic>
#include <cstdlib>
#include <cstring> // For memcpy
#include <dlfcn.h>
#include <mutex>
#include <sys/system_properties.h>
namespace android {
// Helper function to get the Android API level at runtime.
// It caches the result for performance.
int32_t get_android_api_level() {
static std::atomic<int32_t> api_level = -1;
if (api_level.load(std::memory_order_relaxed) == -1) {
char sdk_version_str[PROP_VALUE_MAX];
if (__system_property_get("ro.build.version.sdk", sdk_version_str) > 0) {
api_level.store(atoi(sdk_version_str), std::memory_order_relaxed);
}
}
return api_level.load(std::memory_order_relaxed);
}
// Define the function pointer type for the const member function
// RefBase::incStrongRequireStrong.
using incStrongRequireStrong_t = void (RefBase::*)(const void *) const;
// This is the implementation of our compatibility wrapper.
void incStrongFromExisting(const RefBase *ref, const void *id) {
// Only attempt to use the new function on Android 12 (API 31) or higher.
if (get_android_api_level() >= 31) {
static incStrongRequireStrong_t sIncStrongRequireStrong = nullptr;
static std::once_flag sFlag;
// Thread-safe, one-time initialization.
std::call_once(sFlag, []() {
// Find the symbol in the already loaded libraries.
// The mangled symbol is _ZNK7android7RefBase22incStrongRequireStrongEPKv
void *sym = dlsym(RTLD_DEFAULT,
"_ZNK7android7RefBase22incStrongRequireStrongEPKv");
if (sym) {
// Safely cast the void* symbol to our member function pointer.
memcpy(&sIncStrongRequireStrong, &sym, sizeof(void *));
}
});
if (sIncStrongRequireStrong) {
// If the symbol was found, call it as member function.
(ref->*sIncStrongRequireStrong)(id);
return; // Success, we are done.
}
// If dlsym failed for any reason, we fall through to the old method.
}
// Fallback for older Android versions or if dlsym failed.
// This calls the universally available incStrong method.
ref->incStrong(id);
}
} // namespace android
+11
View File
@@ -0,0 +1,11 @@
#pragma once
namespace android {
// Forward-declare the RefBase class.
class RefBase;
// Declares our compatibility function.
void incStrongFromExisting(const RefBase *ref, const void *id);
} // namespace android
@@ -0,0 +1,83 @@
/*
* 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
// ---------------------------------------------------------------------------
+4 -1
View File
@@ -39,8 +39,11 @@
//
// For a more detailed explanation of this strategy, see
// https://www.gnu.org/software/gnulib/manual/html_node/Exported-Symbols-of-Shared-Libraries.html
#if BUILDING_LIBBINDER
#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
// same way as LIBBINDER_EXPORTED, only exists to help track what we want
@@ -0,0 +1,99 @@
/*
* 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
@@ -0,0 +1,42 @@
/*
* 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
@@ -0,0 +1,71 @@
/*
* 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,6 +263,7 @@ constexpr const char* const kManualInterfaces[] = {
"android.utils.IMemory",
"android.utils.IMemoryHeap",
"com.android.car.procfsinspector.IProcfsInspector",
"com.android.internal.app.IAppOpsCallback",
"com.android.internal.app.IAppOpsService",
"com.android.internal.app.IBatteryStats",
"com.android.internal.os.IResultReceiver",
+122
View File
@@ -0,0 +1,122 @@
/*
* 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,10 +64,7 @@ public:
* Returns the PID of the process which has made the current binder
* call. If not in a binder call, this will return getpid.
*
* 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
* Warning: oneway transactions do not receive PID. Even if you expect
* a transaction to be synchronous, a misbehaving client could send it
* 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
@@ -0,0 +1,69 @@
/*
* 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__
@@ -0,0 +1,50 @@
/*
* 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
@@ -0,0 +1,25 @@
/*
* 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
@@ -0,0 +1,29 @@
/*
* 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
@@ -0,0 +1,51 @@
/*
* 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
@@ -0,0 +1,114 @@
/*
* 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
@@ -0,0 +1,48 @@
/*
* 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
@@ -0,0 +1,61 @@
/*
* 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
@@ -0,0 +1,111 @@
/*
* 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
+55 -9
View File
@@ -26,6 +26,9 @@
#include <vector>
#include <binder/unique_fd.h>
#ifndef BINDER_DISABLE_NATIVE_HANDLE
#include <cutils/native_handle.h>
#endif
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <utils/String16.h>
@@ -48,6 +51,7 @@ template <typename T> class LightFlattenable;
class IBinder;
class IPCThreadState;
class ProcessState;
class RpcSession;
class String8;
class TextOutput;
namespace binder {
@@ -59,6 +63,7 @@ class RecordedTransaction;
class Parcel {
friend class IPCThreadState;
friend class RpcState;
public:
class ReadableBlob;
@@ -121,6 +126,10 @@ public:
// is for an RPC transaction).
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
// markForBinder or markForRpc).
LIBBINDER_EXPORTED bool isForRpc() const;
@@ -338,6 +347,14 @@ public:
template<typename T>
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
// valid for the lifetime of the parcel.
// The Parcel does not take ownership of the given fd unless you ask it to.
@@ -584,6 +601,14 @@ public:
// response headers rather than doing it by hand.
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
// in the parcel, which you do not own -- use dup() to get your own copy.
LIBBINDER_EXPORTED int readFileDescriptor() const;
@@ -626,11 +651,6 @@ public:
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:
// Close all file descriptors in the parcel at object positions >= newObjectsSize.
void closeFileDescriptors(size_t newObjectsSize);
@@ -645,10 +665,16 @@ private:
size_t ipcObjectsCount() const;
void ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
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);
void releaseObjects();
void reacquireObjects(size_t objectSize);
void acquireObjects();
status_t growData(size_t len);
// Clear the Parcel and set the capacity to `desired`.
// Doesn't reset the RPC session association.
@@ -1319,10 +1345,30 @@ private:
mutable bool mFdsKnown = true;
mutable bool mHasFds = false;
};
// TrickyStoreOSS stub
struct RpcFields {};
// Fields only needed when parcelling for RPC Binder.
struct RpcFields {
RpcFields(const sp<RpcSession>& session);
// 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;
// Pointer to KernelFields in mVariantFields if present.
@@ -0,0 +1,70 @@
/*
* 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
@@ -0,0 +1,146 @@
/*
* 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
@@ -0,0 +1,86 @@
/*
* 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__
@@ -0,0 +1,65 @@
/*
* 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__
@@ -0,0 +1,130 @@
/*
* 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
@@ -0,0 +1,89 @@
/*
* 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
@@ -0,0 +1,41 @@
/*
* 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
@@ -0,0 +1,41 @@
/*
* 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
@@ -0,0 +1,297 @@
/*
* 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
@@ -0,0 +1,413 @@
/*
* 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
@@ -0,0 +1,207 @@
/*
* 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
@@ -0,0 +1,42 @@
/*
* 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
@@ -0,0 +1,726 @@
/*
* 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
@@ -0,0 +1,176 @@
/*
* 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
@@ -0,0 +1,205 @@
/*
* 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
@@ -0,0 +1,59 @@
/*
* 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
+2 -4
View File
@@ -221,7 +221,7 @@
// LightRefBase used to be declared in this header, so we have to include it
#include <utils/LightRefBase.h>
#include "utils/StrongPointer.h"
#include <utils/StrongPointer.h>
#include <utils/TypeHelpers.h>
// ---------------------------------------------------------------------------
@@ -542,9 +542,7 @@ wp<T> wp<T>::fromExisting(T* other) {
if (!other) return nullptr;
auto refs = other->getWeakRefs();
//TrickyStoreOSS edit
//refs->incWeakRequireWeak(other);
refs->incWeak(other);
refs->incWeakRequireWeak(other);
wp<T> ret;
ret.m_ptr = other;
@@ -17,6 +17,7 @@
#ifndef ANDROID_STRONG_POINTER_H
#define ANDROID_STRONG_POINTER_H
#include "refbase_compat.h"
#include <functional>
#include <type_traits> // for common_type.
@@ -30,7 +31,7 @@ template<typename T> class wp;
template<typename T>
class sp {
public:
inline sp() : m_ptr(nullptr) { }
inline constexpr sp() : m_ptr(nullptr) { }
// 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
@@ -212,7 +213,7 @@ sp<T> sp<T>::make(Args&&... args) {
template <typename T>
sp<T> sp<T>::fromExisting(T* other) {
if (other) {
other->incStrongRequireStrong(other);
incStrongFromExisting(other, other);
sp<T> result;
result.m_ptr = other;
return result;
+4
View File
@@ -20,6 +20,7 @@
#include <stdint.h>
#include <sys/types.h>
// #include <log/log.h>
#include <utils/TypeHelpers.h>
#include <utils/VectorImpl.h>
#ifndef __has_attribute
@@ -272,6 +273,9 @@ TYPE* Vector<TYPE>::editArray() {
template<class TYPE> inline
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);
}
@@ -38,10 +38,58 @@ enum {
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_ACCEPTS_FDS: whether the node accepts fds.
*/
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
*
@@ -589,4 +637,3 @@ enum binder_driver_command_protocol {
};
#endif /* _UAPI_LINUX_BINDER_H */
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <android/log.h>
#include <errno.h>
#ifndef LOG_TAG
#define LOG_TAG "TEESimulator"
#endif
#include "../logging.hpp"
+499
View File
@@ -0,0 +1,499 @@
#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
@@ -1,140 +0,0 @@
// 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)";
}
@@ -1,33 +0,0 @@
// 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
@@ -1,36 +0,0 @@
// 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
+5 -3
View File
@@ -1,14 +1,16 @@
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "binder/Binder.h"
#include "binder/BpBinder.h"
#include "binder/IInterface.h"
#include "binder/IPCThreadState.h"
#include "binder/IServiceManager.h"
#include "binder/RpcSession.h"
#include "binder/Status.h"
namespace android {
IInterface::IInterface() {}
IInterface::~IInterface() {}
IBinder::IBinder() {}
IBinder::~IBinder() {}
sp<IInterface> IBinder::queryLocalInterface(const String16 &) {
+3 -3
View File
@@ -1,8 +1,6 @@
// Copyright 2025 Dakkshesh <beakthoven@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "utils/RefBase.h"
#include "utils/String16.h"
#include "utils/String8.h"
#include "utils/StrongPointer.h"
namespace android {
@@ -50,6 +48,8 @@ bool RefBase::weakref_type::attemptIncWeak(const void *id) {
void sp_report_race() {}
String8::String8() {}
String16::String16() {}
String16::String16(const String16 &o) {}
@@ -1,239 +0,0 @@
/*
* 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) }
@@ -1,156 +0,0 @@
/*
* 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
}
}
}
@@ -1,555 +0,0 @@
/*
* 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)
}
}
@@ -1,266 +0,0 @@
/*
* 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)
}
}
@@ -1,225 +0,0 @@
/*
* 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
}
}
}
@@ -1,59 +0,0 @@
/*
* 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)
}
}
@@ -1,363 +0,0 @@
/*
* 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
}
}
}
@@ -1,298 +0,0 @@
/*
* 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,
)
@@ -1,199 +0,0 @@
/*
* 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()
}
}
}
}
@@ -1,153 +0,0 @@
/*
* 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
}
}
@@ -1,198 +0,0 @@
/*
* 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
}
}
@@ -1,296 +0,0 @@
/*
* 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
}
}
@@ -1,207 +0,0 @@
/*
* 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
}
}
@@ -1,73 +0,0 @@
/*
* 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())
}
}
}
@@ -0,0 +1,131 @@
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.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 sleep duration in milliseconds for the main service loop to keep the process alive.
private const val SERVICE_SLEEP_MS = 1000000L
/**
* The main entry point of the TEESimulator application.
*
* @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())
// 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
}
}
}
@@ -0,0 +1,403 @@
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)
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)
private 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)
}
}
@@ -0,0 +1,93 @@
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
}
@@ -0,0 +1,312 @@
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?,
)
}
@@ -0,0 +1,271 @@
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
}
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
}
}
}
@@ -0,0 +1,174 @@
package org.matrix.TEESimulator.attestation
import android.hardware.security.keymint.*
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 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 = 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) }
}
}
// --- 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 = 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
}
}
@@ -0,0 +1,388 @@
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.
val newGlobalLevel = parseLines(contextLines[""])
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 -> loadTargetPackages(file!!)
PATCH_LEVEL_FILE -> loadPatchLevelConfig(file!!)
// 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?,
)
@@ -0,0 +1,329 @@
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()
}
}
}
}
@@ -0,0 +1,141 @@
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.
}
}
@@ -0,0 +1,113 @@
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 {
/**
* 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 {
return runCatching { reply.readException() }.exceptionOrNull() != null
}
}
@@ -0,0 +1,299 @@
package org.matrix.TEESimulator.interception.keystore
import android.annotation.SuppressLint
import android.hardware.security.keymint.KeyOrigin
import android.hardware.security.keymint.SecurityLevel
import android.hardware.security.keymint.Tag
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.cert.Certificate
import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.config.ConfigurationManager
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.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)
}
}
.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)
}
}
.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)
if (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
SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}")
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) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction
if (!ConfigurationManager.shouldPatch(callingUid))
return TransactionResult.SkipTransaction
SystemLogger.info("Handling post-${transactionNames[code]!!} ${keyDescriptor.alias}")
return try {
val response =
reply.readTypedObject(KeyEntryResponse.CREATOR)
?: return TransactionResult.SkipTransaction
reply.setDataPosition(0) // Reset for potential reuse.
val originalChain = CertificateHelper.getCertificateChain(response)
val authorizations = response.metadata?.authorizations
val origin =
authorizations
?.find { it.keyParameter.tag == Tag.ORIGIN }
?.let { it.keyParameter.value.origin }
if (origin == KeyOrigin.IMPORTED || origin == KeyOrigin.SECURELY_IMPORTED) {
SystemLogger.info("[TX_ID: $txId] Skip patching for imported keys.")
return TransactionResult.SkipTransaction
}
if (originalChain == null || originalChain.size < 2) {
SystemLogger.info(
"[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}."
)
return TransactionResult.SkipTransaction
}
// Perform the attestation patch.
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
// First, try to retrieve the already-patched chain from our cache to ensure
// consistency.
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 {
// If no chain is cached (e.g., key existed before simulator started),
// perform a live patch as a fallback. This may still be detectable.
SystemLogger.info(
"[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()
InterceptorUtils.createTypedObjectReply(response)
} catch (e: Exception) {
SystemLogger.error("[TX_ID: $txId] Failed to patch certificate chain.", e)
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
SystemLogger.verbose(
"Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]"
)
return InterceptorUtils.createSuccessReply(writeResultCode = false)
}
}
@@ -0,0 +1,495 @@
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, // Not explicitly available in legacy args, but not critical
ecCurveName = this.ecCurveName ?: "",
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()
}
}
}
@@ -0,0 +1,142 @@
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
}
}
}
}

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