Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1bb3bbfa3 | ||
|
|
e13adb925d | ||
|
|
c3f8f087a6 | ||
|
|
51f32b9db2 | ||
|
|
d60ad8fe47 | ||
|
|
9a1fbe8c79 | ||
|
|
68b660dfe1 | ||
|
|
068188503c | ||
|
|
1bbc50d138 | ||
|
|
d2492df02e | ||
|
|
e7d7b21daa | ||
|
|
c29bc35a36 | ||
|
|
549b5cecc2 | ||
|
|
04d003ff4d | ||
|
|
0a842c6e07 | ||
|
|
9f77771e7b | ||
|
|
ab4fe643a3 | ||
|
|
ce740542f7 | ||
|
|
c27523fd97 | ||
|
|
5a8454af7b | ||
|
|
83b65f09c9 | ||
|
|
2a76b18308 | ||
|
|
d9e47712f3 | ||
|
|
d846de4332 | ||
|
|
13d89c4314 | ||
|
|
00c91adfaa | ||
|
|
119350f24b | ||
|
|
7d4c753d66 | ||
|
|
b988d04971 | ||
|
|
d0cc5e3b56 | ||
|
|
e66e558ce5 | ||
|
|
8d431cc946 | ||
|
|
30746892b0 | ||
|
|
28cfe70a85 | ||
|
|
65a613ae0e | ||
|
|
9146b86648 | ||
|
|
457a58da04 | ||
|
|
b2838ac04b | ||
|
|
a7534feac7 | ||
|
|
4e67371193 | ||
|
|
2ef89f15c6 | ||
|
|
4f608247fe | ||
|
|
22cbe5a9a7 | ||
|
|
a6fa137e32 | ||
|
|
5afefba7bd | ||
|
|
ba9578c59b | ||
|
|
733e64c3cb | ||
|
|
7f94ba4b5b | ||
|
|
fa1d9ecc56 | ||
|
|
eec9e77631 | ||
|
|
d18692fbef | ||
|
|
13b4786cd9 | ||
|
|
612de6cdf2 | ||
|
|
020a930a31 | ||
|
|
0c1937bd3e | ||
|
|
95262d4b58 | ||
|
|
ad4e772eb4 | ||
|
|
2ac2518216 | ||
|
|
79de0be122 |
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Generated
-3
@@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
Generated
-1
@@ -1 +0,0 @@
|
||||
Tricky Store OSS
|
||||
Generated
-6
@@ -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>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-10
@@ -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>
|
||||
Generated
-20
@@ -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>
|
||||
Generated
-10
@@ -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>
|
||||
Generated
-9
@@ -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>
|
||||
Generated
-17
@@ -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
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
/build
|
||||
+25
-51
@@ -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")
|
||||
|
||||
Vendored
+3
-88
@@ -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,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 />
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
+29
@@ -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
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
Submodule app/src/main/cpp/external/LSPlt updated: 2efebed64e...3e29437f03
@@ -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 */
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef LOG_TAG
|
||||
#define LOG_TAG "TEESimulator"
|
||||
#endif
|
||||
|
||||
#include "../logging.hpp"
|
||||
@@ -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 ®s);
|
||||
|
||||
/**
|
||||
* @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 ®s);
|
||||
|
||||
// --- 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 ®s, 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 ®s, 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 ®s, 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 ®s, 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 ®s, 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 ®s, 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)";
|
||||
}
|
||||
+700
-173
File diff suppressed because it is too large
Load Diff
+738
-394
File diff suppressed because it is too large
Load Diff
@@ -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 ®s);
|
||||
bool set_regs(int pid, struct user_regs_struct ®s);
|
||||
|
||||
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 ®s, uintptr_t preserve_bytes = 0);
|
||||
uintptr_t push_memory(int pid, struct user_regs_struct ®s, const void *data, size_t length);
|
||||
uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str);
|
||||
|
||||
uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<uintptr_t> &args);
|
||||
bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, std::vector<uintptr_t> &args);
|
||||
uintptr_t remote_post_call(int pid, struct user_regs_struct ®s, 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
|
||||
@@ -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
|
||||
@@ -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 &) {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
-199
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-153
@@ -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
|
||||
}
|
||||
}
|
||||
-198
@@ -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
|
||||
}
|
||||
}
|
||||
-296
@@ -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
|
||||
}
|
||||
}
|
||||
-207
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+141
@@ -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
|
||||
}
|
||||
}
|
||||
+299
@@ -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)
|
||||
}
|
||||
}
|
||||
+495
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -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
Reference in New Issue
Block a user