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 |
@@ -60,7 +60,8 @@ jobs:
|
|||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./gradlew
|
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
|
- name: Prepare artifact
|
||||||
if: success()
|
if: success()
|
||||||
|
|||||||
@@ -76,12 +76,65 @@ org.matrix.demo
|
|||||||
|
|
||||||
### Security Patch Level (`security_patch.txt`)
|
### 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
|
# --- Global Configuration ---
|
||||||
system=2025-11
|
# This is the default for all apps unless specified otherwise.
|
||||||
boot=no # Do not report a boot patch level
|
# - Forge a recent system patch level, the 5th of the current month (a common patch date).
|
||||||
vendor=20251101 # Report a specific vendor patch level
|
# - 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
|
||||||
|
|
||||||
|
# --- 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
|
||||||
```
|
```
|
||||||
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change system properties.
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ val gitExecutor = objects.newInstance(GitExecutor::class.java)
|
|||||||
|
|
||||||
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
|
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
|
||||||
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
|
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
|
||||||
val verName = "v2.1"
|
val verName = "v3.1"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.matrix.TEESimulator"
|
namespace = "org.matrix.TEESimulator"
|
||||||
@@ -56,6 +56,7 @@ android {
|
|||||||
sourceCompatibility = JavaVersion.VERSION_21
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
|
buildFeatures { buildConfig = true }
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path = file("src/main/cpp/CMakeLists.txt")
|
path = file("src/main/cpp/CMakeLists.txt")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ add_subdirectory(external/LSPlt/lsplt/src/main/jni)
|
|||||||
|
|
||||||
add_compile_definitions(BINDER_DISABLE_NATIVE_HANDLE)
|
add_compile_definitions(BINDER_DISABLE_NATIVE_HANDLE)
|
||||||
add_library(utils SHARED stub/stub_utils.cpp)
|
add_library(utils SHARED stub/stub_utils.cpp)
|
||||||
target_include_directories(utils PUBLIC external/AOSP/include)
|
target_include_directories(utils PUBLIC external/AOSP/include compat)
|
||||||
|
|
||||||
add_library(binder SHARED stub/stub_binder.cpp)
|
add_library(binder SHARED stub/stub_binder.cpp)
|
||||||
target_include_directories(binder PUBLIC external/AOSP/include)
|
target_include_directories(binder PUBLIC external/AOSP/include)
|
||||||
@@ -22,7 +22,7 @@ add_executable(libinject.so inject/main.cpp inject/utils.cpp)
|
|||||||
target_include_directories(libinject.so PUBLIC include)
|
target_include_directories(libinject.so PUBLIC include)
|
||||||
target_link_libraries(libinject.so PRIVATE lsplt_static)
|
target_link_libraries(libinject.so PRIVATE lsplt_static)
|
||||||
|
|
||||||
add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp)
|
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_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include include)
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE binder lsplt_static utils)
|
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE binder lsplt_static utils)
|
||||||
|
|
||||||
|
|||||||
@@ -359,9 +359,15 @@ void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {
|
|||||||
info.transaction_code = intercept::kBackdoorCode;
|
info.transaction_code = intercept::kBackdoorCode;
|
||||||
info.target_binder = nullptr;
|
info.target_binder = nullptr;
|
||||||
hijack = true;
|
hijack = true;
|
||||||
}
|
// Check 2: Spoof uid of KeyStore requests from the daemon to bypass permission check
|
||||||
// Check 2: Normal interception based on registry of monitored binders
|
} else if (txn_data->sender_euid == 0) {
|
||||||
else {
|
// The kernel driver fills sender_euid.
|
||||||
|
// libbinder.so trusts this value to populate IPCThreadState.
|
||||||
|
txn_data->sender_euid = 1000;
|
||||||
|
LOGV("[Hook] Spoofing UID for transaction: 0 -> %d", txn_data->sender_euid);
|
||||||
|
hijack = false; // Never hijack to avoid recursion
|
||||||
|
// Check 3: Normal interception based on registry of monitored binders
|
||||||
|
} else {
|
||||||
// Safe casting based on Binder driver ABI
|
// Safe casting based on Binder driver ABI
|
||||||
RefBase::weakref_type *weak_ref = reinterpret_cast<RefBase::weakref_type *>(txn_data->target.ptr);
|
RefBase::weakref_type *weak_ref = reinterpret_cast<RefBase::weakref_type *>(txn_data->target.ptr);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
#ifndef ANDROID_STRONG_POINTER_H
|
#ifndef ANDROID_STRONG_POINTER_H
|
||||||
#define ANDROID_STRONG_POINTER_H
|
#define ANDROID_STRONG_POINTER_H
|
||||||
|
|
||||||
|
#include "refbase_compat.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <type_traits> // for common_type.
|
#include <type_traits> // for common_type.
|
||||||
|
|
||||||
@@ -212,7 +213,7 @@ sp<T> sp<T>::make(Args&&... args) {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
sp<T> sp<T>::fromExisting(T* other) {
|
sp<T> sp<T>::fromExisting(T* other) {
|
||||||
if (other) {
|
if (other) {
|
||||||
other->incStrongRequireStrong(other);
|
incStrongFromExisting(other, other);
|
||||||
sp<T> result;
|
sp<T> result;
|
||||||
result.m_ptr = other;
|
result.m_ptr = other;
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/ptrace.h>
|
#include <sys/ptrace.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/system_properties.h>
|
#include <sys/system_properties.h>
|
||||||
#include <sys/uio.h>
|
#include <sys/uio.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -95,10 +97,6 @@ constexpr size_t kMagicLength = 16;
|
|||||||
constexpr size_t kMaxPathLength = PATH_MAX;
|
constexpr size_t kMaxPathLength = PATH_MAX;
|
||||||
// Maximum length for file paths.
|
// Maximum length for file paths.
|
||||||
|
|
||||||
constexpr const char *kSystemFileContext = "u:object_r:system_file:s0";
|
|
||||||
// SELinux context for system files,
|
|
||||||
// used for socket creation and library file context.
|
|
||||||
|
|
||||||
constexpr const char *kLibcModule = "libc.so";
|
constexpr const char *kLibcModule = "libc.so";
|
||||||
// Name of the C standard library.
|
// Name of the C standard library.
|
||||||
|
|
||||||
@@ -215,8 +213,8 @@ private:
|
|||||||
* @brief Transfers a file descriptor from the injector process to the remote process.
|
* @brief Transfers a file descriptor from the injector process to the remote process.
|
||||||
*
|
*
|
||||||
* This function uses Unix domain sockets with SCM_RIGHTS to send a file descriptor.
|
* This function uses Unix domain sockets with SCM_RIGHTS to send a file descriptor.
|
||||||
* It involves setting SELinux contexts, creating local and remote sockets, binding,
|
* It involves creating local and remote sockets, binding, and then coordinating
|
||||||
* and then coordinating sendmsg/recvmsg calls using ptrace.
|
* sendmsg/recvmsg calls using ptrace.
|
||||||
*
|
*
|
||||||
* @param pid The target process ID.
|
* @param pid The target process ID.
|
||||||
* @param lib_path The path to the library file being transferred.
|
* @param lib_path The path to the library file being transferred.
|
||||||
@@ -233,29 +231,14 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
uintptr_t libc_return_addr) {
|
uintptr_t libc_return_addr) {
|
||||||
LOGD("Attempting to transfer file descriptor for library: %s", lib_path);
|
LOGD("Attempting to transfer file descriptor for library: %s", lib_path);
|
||||||
|
|
||||||
// 1. Set SELinux context for socket creation in the injector process.
|
// Create a local Unix domain socket for FD transfer.
|
||||||
// This is crucial for Android where SELinux might prevent socket operations.
|
|
||||||
if (!set_sockcreate_con(constants::kSystemFileContext)) {
|
|
||||||
LOGE("Failed to set socket creation context.");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Create a local Unix domain socket for FD transfer.
|
|
||||||
UniqueFd local_socket = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
UniqueFd local_socket = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||||
if (local_socket == -1) {
|
if (local_socket == -1) {
|
||||||
PLOGE("Failed to create local Unix domain socket.");
|
PLOGE("Failed to create local Unix domain socket.");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Set SELinux context for the library file if possible.
|
// Open the local library file to get a file descriptor.
|
||||||
// This might be required for the target process to open/access it later if directly opening by path.
|
|
||||||
// For FD transfer, this is less critical as the FD's context is inherited, but good practice.
|
|
||||||
if (setfilecon(lib_path, constants::kSystemFileContext) == -1) {
|
|
||||||
// Log a warning, but don't fail, as FD transfer might still work.
|
|
||||||
PLOGE("Failed to set context of library file: %s. This might cause issues.", lib_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Open the local library file to get a file descriptor.
|
|
||||||
UniqueFd local_lib_fd = open(lib_path, O_RDONLY | O_CLOEXEC);
|
UniqueFd local_lib_fd = open(lib_path, O_RDONLY | O_CLOEXEC);
|
||||||
if (local_lib_fd == -1) {
|
if (local_lib_fd == -1) {
|
||||||
PLOGE("Failed to open library file: %s", lib_path);
|
PLOGE("Failed to open library file: %s", lib_path);
|
||||||
@@ -271,7 +254,7 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
void *errno_addr; // Address of __errno for getting remote errno.
|
void *errno_addr; // Address of __errno for getting remote errno.
|
||||||
} funcs{};
|
} funcs{};
|
||||||
|
|
||||||
// 5. Resolve required libc functions in the remote process.
|
// Resolve required libc functions in the remote process.
|
||||||
funcs.socket_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "socket");
|
funcs.socket_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "socket");
|
||||||
funcs.bind_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "bind");
|
funcs.bind_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "bind");
|
||||||
funcs.recvmsg_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "recvmsg");
|
funcs.recvmsg_addr = find_func_addr(local_map, remote_map, constants::kLibcModule, "recvmsg");
|
||||||
@@ -306,25 +289,28 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 6. Create a Unix domain socket in the remote process.
|
// Create a Unix domain socket in the remote process.
|
||||||
std::vector<uintptr_t> args = {AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0};
|
std::vector<uintptr_t> args = {AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0};
|
||||||
int remote_fd = static_cast<int>(
|
int remote_fd = static_cast<int>(
|
||||||
remote_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.socket_addr), libc_return_addr, args));
|
remote_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.socket_addr), libc_return_addr, args));
|
||||||
if (remote_fd == -1) {
|
if (remote_fd <= 0) {
|
||||||
|
// remote_call returns 0 on failure.
|
||||||
|
// socket() returning 0 is technically possible (if stdin closed),
|
||||||
|
// but highly unlikely for a daemon. We treat 0 as failure here to catch the injection error.
|
||||||
errno = get_remote_errno(); // Set local errno for PLOGE.
|
errno = get_remote_errno(); // Set local errno for PLOGE.
|
||||||
PLOGE("Failed to create remote socket.");
|
PLOGE("Failed to create remote socket (returned %d).", remote_fd);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
LOGD("Successfully created remote socket with FD: %d", remote_fd);
|
LOGD("Successfully created remote socket with FD: %d", remote_fd);
|
||||||
|
|
||||||
// 7. Generate a unique magic string for the abstract Unix domain socket path.
|
// Generate a unique magic string for the abstract Unix domain socket path.
|
||||||
auto magic = generateMagic(constants::kMagicLength);
|
auto magic = generateMagic(constants::kMagicLength);
|
||||||
struct sockaddr_un sock_addr{.sun_family = AF_UNIX, .sun_path = {0}};
|
struct sockaddr_un sock_addr{.sun_family = AF_UNIX, .sun_path = {0}};
|
||||||
// Abstract Unix domain sockets have sun_path[0] as null, and the name starts from sun_path[1].
|
// Abstract Unix domain sockets have sun_path[0] as null, and the name starts from sun_path[1].
|
||||||
memcpy(sock_addr.sun_path + 1, magic.c_str(), magic.size());
|
memcpy(sock_addr.sun_path + 1, magic.c_str(), magic.size());
|
||||||
socklen_t addr_len = sizeof(sock_addr.sun_family) + 1 + magic.size(); // Length includes null byte and magic.
|
socklen_t addr_len = sizeof(sock_addr.sun_family) + 1 + magic.size(); // Length includes null byte and magic.
|
||||||
|
|
||||||
// 8. Push the sockaddr_un structure to the remote process's stack.
|
// Push the sockaddr_un structure to the remote process's stack.
|
||||||
auto remote_addr = push_memory(pid, regs, &sock_addr, sizeof(sock_addr));
|
auto remote_addr = push_memory(pid, regs, &sock_addr, sizeof(sock_addr));
|
||||||
if (remote_addr == 0) {
|
if (remote_addr == 0) {
|
||||||
LOGE("Failed to push socket address to remote memory.");
|
LOGE("Failed to push socket address to remote memory.");
|
||||||
@@ -332,7 +318,7 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. Bind the remote socket to the abstract Unix domain socket path.
|
// Bind the remote socket to the abstract Unix domain socket path.
|
||||||
args = {static_cast<uintptr_t>(remote_fd), remote_addr, static_cast<uintptr_t>(addr_len)};
|
args = {static_cast<uintptr_t>(remote_fd), remote_addr, static_cast<uintptr_t>(addr_len)};
|
||||||
auto bind_result = remote_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.bind_addr), libc_return_addr, args);
|
auto bind_result = remote_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.bind_addr), libc_return_addr, args);
|
||||||
if (bind_result == static_cast<uintptr_t>(-1)) {
|
if (bind_result == static_cast<uintptr_t>(-1)) {
|
||||||
@@ -346,7 +332,7 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
// Prepare control message buffer for SCM_RIGHTS (file descriptor passing).
|
// Prepare control message buffer for SCM_RIGHTS (file descriptor passing).
|
||||||
char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0};
|
char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0};
|
||||||
|
|
||||||
// 10. Push the control message buffer to the remote process's stack.
|
// Push the control message buffer to the remote process's stack.
|
||||||
auto remote_cmsgbuf = push_memory(pid, regs, &cmsgbuf, sizeof(cmsgbuf));
|
auto remote_cmsgbuf = push_memory(pid, regs, &cmsgbuf, sizeof(cmsgbuf));
|
||||||
if (remote_cmsgbuf == 0) {
|
if (remote_cmsgbuf == 0) {
|
||||||
LOGE("Failed to push control message buffer to remote memory.");
|
LOGE("Failed to push control message buffer to remote memory.");
|
||||||
@@ -359,7 +345,7 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
msg_hdr.msg_control = reinterpret_cast<void *>(remote_cmsgbuf);
|
msg_hdr.msg_control = reinterpret_cast<void *>(remote_cmsgbuf);
|
||||||
msg_hdr.msg_controllen = sizeof(cmsgbuf);
|
msg_hdr.msg_controllen = sizeof(cmsgbuf);
|
||||||
|
|
||||||
// 11. Push the msghdr structure to the remote process's stack.
|
// Push the msghdr structure to the remote process's stack.
|
||||||
auto remote_hdr = push_memory(pid, regs, &msg_hdr, sizeof(msg_hdr));
|
auto remote_hdr = push_memory(pid, regs, &msg_hdr, sizeof(msg_hdr));
|
||||||
if (remote_hdr == 0) {
|
if (remote_hdr == 0) {
|
||||||
LOGE("Failed to push message header to remote memory.");
|
LOGE("Failed to push message header to remote memory.");
|
||||||
@@ -367,16 +353,16 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12. Initiate the remote recvmsg call. This will block the remote process.
|
// Initiate the remote recvmsg call. This will block the remote process.
|
||||||
args = {static_cast<uintptr_t>(remote_fd), remote_hdr, MSG_WAITALL};
|
args = {static_cast<uintptr_t>(remote_fd), remote_hdr, MSG_WAITALL};
|
||||||
if (!remote_pre_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.recvmsg_addr), 0, args)) {
|
if (!remote_pre_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.recvmsg_addr), libc_return_addr, args)) {
|
||||||
LOGE("Failed to initiate remote recvmsg call.");
|
LOGE("Failed to initiate remote recvmsg call.");
|
||||||
close_remote(remote_fd);
|
close_remote(remote_fd);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
LOGD("Remote recvmsg initiated, waiting for FD transfer...");
|
LOGD("Remote recvmsg initiated, waiting for FD transfer...");
|
||||||
|
|
||||||
// 13. Prepare the local msghdr for sending the file descriptor.
|
// Prepare the local msghdr for sending the file descriptor.
|
||||||
// The msg_control and msg_name fields of the local msghdr are set up.
|
// The msg_control and msg_name fields of the local msghdr are set up.
|
||||||
msg_hdr.msg_control = &cmsgbuf; // Use local cmsgbuf for sending.
|
msg_hdr.msg_control = &cmsgbuf; // Use local cmsgbuf for sending.
|
||||||
msg_hdr.msg_name = &sock_addr;
|
msg_hdr.msg_name = &sock_addr;
|
||||||
@@ -396,7 +382,7 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
*reinterpret_cast<int *>(CMSG_DATA(cmsg)) = local_lib_fd; // The FD to send.
|
*reinterpret_cast<int *>(CMSG_DATA(cmsg)) = local_lib_fd; // The FD to send.
|
||||||
}
|
}
|
||||||
|
|
||||||
// 14. Send the file descriptor from the injector to the remote process.
|
// Send the file descriptor from the injector to the remote process.
|
||||||
if (sendmsg(local_socket, &msg_hdr, 0) == -1) {
|
if (sendmsg(local_socket, &msg_hdr, 0) == -1) {
|
||||||
PLOGE("Failed to send file descriptor to remote process.");
|
PLOGE("Failed to send file descriptor to remote process.");
|
||||||
// We do not close local_lib_fd here as it might be transferred even if
|
// We do not close local_lib_fd here as it might be transferred even if
|
||||||
@@ -407,9 +393,9 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
}
|
}
|
||||||
LOGD("Local FD %d sent to remote process.", local_lib_fd.operator const int &());
|
LOGD("Local FD %d sent to remote process.", local_lib_fd.operator const int &());
|
||||||
|
|
||||||
// 15. Complete the remote recvmsg call. This will retrieve the return value.
|
// Complete the remote recvmsg call. This will retrieve the return value.
|
||||||
auto recvmsg_result =
|
auto recvmsg_result =
|
||||||
static_cast<ssize_t>(remote_post_call(pid, regs, 0)); // No specific expected return address for recvmsg
|
static_cast<ssize_t>(remote_post_call(pid, regs, libc_return_addr));
|
||||||
if (recvmsg_result == -1) {
|
if (recvmsg_result == -1) {
|
||||||
errno = get_remote_errno();
|
errno = get_remote_errno();
|
||||||
PLOGE("Remote recvmsg call failed.");
|
PLOGE("Remote recvmsg call failed.");
|
||||||
@@ -418,7 +404,7 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
}
|
}
|
||||||
LOGD("Remote recvmsg completed with result: %zd", recvmsg_result);
|
LOGD("Remote recvmsg completed with result: %zd", recvmsg_result);
|
||||||
|
|
||||||
// 16. Read the control message buffer back from the remote process to extract the FD.
|
// Read the control message buffer back from the remote process to extract the FD.
|
||||||
if (read_proc(pid, remote_cmsgbuf, &cmsgbuf, sizeof(cmsgbuf)) != sizeof(cmsgbuf)) {
|
if (read_proc(pid, remote_cmsgbuf, &cmsgbuf, sizeof(cmsgbuf)) != sizeof(cmsgbuf)) {
|
||||||
LOGE("Failed to read control message buffer from remote process.");
|
LOGE("Failed to read control message buffer from remote process.");
|
||||||
close_remote(remote_fd);
|
close_remote(remote_fd);
|
||||||
@@ -439,7 +425,7 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
|
|||||||
LOGI("Successfully transferred FD %d to remote process, new remote FD: %d", local_lib_fd.operator const int &(),
|
LOGI("Successfully transferred FD %d to remote process, new remote FD: %d", local_lib_fd.operator const int &(),
|
||||||
transferred_fd);
|
transferred_fd);
|
||||||
|
|
||||||
// 17. Close the remote socket.
|
// Close the remote socket.
|
||||||
close_remote(remote_fd);
|
close_remote(remote_fd);
|
||||||
|
|
||||||
return transferred_fd;
|
return transferred_fd;
|
||||||
@@ -642,6 +628,130 @@ static bool remote_call_entry(int pid, struct user_regs_struct ®s, uintptr_t
|
|||||||
return true; // Return true if the call itself completed, regardless of its return value.
|
return true; // Return true if the call itself completed, regardless of its return value.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RAII wrapper to ensure a temporary file is deleted (unlinked)
|
||||||
|
* when the object goes out of scope.
|
||||||
|
*
|
||||||
|
* This is crucial for stealth: we want the library to exist on the filesystem
|
||||||
|
* for the shortest time possible.
|
||||||
|
*/
|
||||||
|
class ScopedFileDeleter {
|
||||||
|
public:
|
||||||
|
explicit ScopedFileDeleter(std::string path) : path_(std::move(path)) {}
|
||||||
|
|
||||||
|
~ScopedFileDeleter() {
|
||||||
|
if (!path_.empty()) {
|
||||||
|
LOGD("Cleaning up staged file: %s", path_.c_str());
|
||||||
|
unlink(path_.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable copy to prevent double-deletion issues
|
||||||
|
ScopedFileDeleter(const ScopedFileDeleter&) = delete;
|
||||||
|
ScopedFileDeleter& operator=(const ScopedFileDeleter&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string path_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copies a file from source to destination.
|
||||||
|
*
|
||||||
|
* @param src Absolute path to source file.
|
||||||
|
* @param dst Absolute path to destination file.
|
||||||
|
* @return True on success, false on failure.
|
||||||
|
*/
|
||||||
|
static bool copy_file(const char* src, const char* dst) {
|
||||||
|
std::ifstream src_file(src, std::ios::binary);
|
||||||
|
std::ofstream dst_file(dst, std::ios::binary);
|
||||||
|
|
||||||
|
if (!src_file) {
|
||||||
|
PLOGE("Failed to open source file for copying: %s", src);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!dst_file) {
|
||||||
|
PLOGE("Failed to open destination file for copying: %s", dst);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dst_file << src_file.rdbuf();
|
||||||
|
return src_file.good() && dst_file.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Performs injection via the "Staging" method.
|
||||||
|
*
|
||||||
|
* This strategy is used when direct FD passing fails (e.g., due to Seccomp filters).
|
||||||
|
* 1. Copies the library to a world-readable location (/data/local/tmp).
|
||||||
|
* 2. Loads it via standard dlopen().
|
||||||
|
* 3. Immediately deletes the file to hide tracks.
|
||||||
|
*
|
||||||
|
* @param pid The target process ID.
|
||||||
|
* @param regs The target process registers (must be Red-Zone adjusted if x86_64).
|
||||||
|
* @param local_map Local memory map.
|
||||||
|
* @param remote_map Remote memory map.
|
||||||
|
* @param lib_path The path to the original library.
|
||||||
|
* @param libc_return_addr Return address for remote calls.
|
||||||
|
* @return The handle of the loaded library, or std::nullopt on failure.
|
||||||
|
*/
|
||||||
|
static std::optional<uintptr_t> inject_via_staging(int pid, struct user_regs_struct ®s,
|
||||||
|
const std::vector<lsplt::MapInfo> &local_map,
|
||||||
|
const std::vector<lsplt::MapInfo> &remote_map,
|
||||||
|
const char *lib_path, uintptr_t libc_return_addr) {
|
||||||
|
LOGI("Initiating Staging Fallback mechanism...");
|
||||||
|
|
||||||
|
// Generate a random path in /data/local/tmp
|
||||||
|
// /data/local/tmp is chosen because it is traversable by most contexts.
|
||||||
|
std::string staged_path = "/data/local/tmp/lib" + generateMagic(8) + ".so";
|
||||||
|
|
||||||
|
// Ensure the file is deleted when this function exits (Success or Failure).
|
||||||
|
// The kernel keeps the inode alive for the mapped process even after unlink.
|
||||||
|
ScopedFileDeleter file_guard(staged_path);
|
||||||
|
|
||||||
|
LOGD("Staging library to: %s", staged_path.c_str());
|
||||||
|
|
||||||
|
// Copy the library
|
||||||
|
if (!copy_file(lib_path, staged_path.c_str())) {
|
||||||
|
LOGE("Failed to copy library during staging.");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Permissions to 644 (RW-R--R--)
|
||||||
|
// This allows the target process (likely running as a specific UID) to read the file.
|
||||||
|
if (chmod(staged_path.c_str(), 0644) != 0) {
|
||||||
|
PLOGE("Failed to chmod staged file.");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve 'dlopen' in the remote process
|
||||||
|
auto dlopen_addr = find_func_addr(local_map, remote_map, constants::kLibdlModule, "dlopen");
|
||||||
|
if (!dlopen_addr) {
|
||||||
|
LOGE("Failed to find 'dlopen' in remote process.");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the staged path to remote memory
|
||||||
|
uintptr_t remote_path_addr = push_string(pid, regs, staged_path.c_str());
|
||||||
|
if (remote_path_addr == 0) {
|
||||||
|
LOGE("Failed to push staged path string to remote memory.");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call dlopen(path, RTLD_NOW)
|
||||||
|
std::vector<uintptr_t> args = {remote_path_addr, RTLD_NOW};
|
||||||
|
uintptr_t handle = remote_call(pid, regs, reinterpret_cast<uintptr_t>(dlopen_addr),
|
||||||
|
libc_return_addr, args);
|
||||||
|
|
||||||
|
if (handle == 0) {
|
||||||
|
std::string error_msg = get_remote_dlerror(pid, regs, local_map, remote_map, libc_return_addr);
|
||||||
|
LOGE("Staged dlopen failed. dlerror: %s", error_msg.c_str());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGI("Successfully loaded staged library. Handle: %p", reinterpret_cast<void*>(handle));
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief RAII wrapper for ptrace attachment and detachment.
|
* @brief RAII wrapper for ptrace attachment and detachment.
|
||||||
*
|
*
|
||||||
@@ -694,12 +804,31 @@ private:
|
|||||||
bool attached_; // Flag indicating current attachment status.
|
bool attached_; // Flag indicating current attachment status.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// RAII Class to ensure registers are always restored
|
||||||
|
class RegisterRestorer {
|
||||||
|
public:
|
||||||
|
RegisterRestorer(int pid, const struct user_regs_struct& original_regs)
|
||||||
|
: pid_(pid), regs_(original_regs) {}
|
||||||
|
|
||||||
|
~RegisterRestorer() {
|
||||||
|
// Always restore registers when this object goes out of scope
|
||||||
|
if (set_regs(pid_, regs_)) {
|
||||||
|
LOGD("Original registers for process %d restored.", pid_);
|
||||||
|
} else {
|
||||||
|
PLOGE("Failed to restore original registers for process %d.", pid_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int pid_;
|
||||||
|
struct user_regs_struct regs_;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Injects a shared library into a target process using ptrace.
|
* @brief Injects a shared library into a target process using ptrace.
|
||||||
*
|
*
|
||||||
* This is the main orchestration function for the library injection.
|
* This is the main orchestration function for the library injection.
|
||||||
* It handles attachment, remote memory/register manipulation, FD transfer,
|
* It handles attachment, remote memory/register manipulation, FD transfer,
|
||||||
* remote dlopen/dlsym, and remote entry point execution.
|
* staging fallback, remote dlopen/dlsym, and remote entry point execution.
|
||||||
*
|
*
|
||||||
* @param pid The target process ID.
|
* @param pid The target process ID.
|
||||||
* @param lib_path The absolute path to the shared library to inject.
|
* @param lib_path The absolute path to the shared library to inject.
|
||||||
@@ -742,6 +871,14 @@ bool inject_library(int pid, const char *lib_path, const char *entry_name) {
|
|||||||
backup_regs = current_regs; // Store a copy for restoration.
|
backup_regs = current_regs; // Store a copy for restoration.
|
||||||
LOGD("Process %d registers backed up.", pid);
|
LOGD("Process %d registers backed up.", pid);
|
||||||
|
|
||||||
|
// Skip the Red Zone (128 bytes) on x86_64 to prevent stack corruption
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
current_regs.rsp -= 128;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Ensures original state is restored even if injection fails/crashes mid-way.
|
||||||
|
RegisterRestorer reg_guard(pid, backup_regs);
|
||||||
|
|
||||||
// Create a scope to ensure RAII objects are destroyed BEFORE register restoration
|
// Create a scope to ensure RAII objects are destroyed BEFORE register restoration
|
||||||
{
|
{
|
||||||
// 4. Scan local and remote memory maps to resolve function addresses.
|
// 4. Scan local and remote memory maps to resolve function addresses.
|
||||||
@@ -760,53 +897,57 @@ bool inject_library(int pid, const char *lib_path, const char *entry_name) {
|
|||||||
}
|
}
|
||||||
LOGD("Found libc return address: %p", reinterpret_cast<void *>(libc_return_addr));
|
LOGD("Found libc return address: %p", reinterpret_cast<void *>(libc_return_addr));
|
||||||
|
|
||||||
// 6. Transfer the library's file descriptor to the remote process.
|
// 6. Attempt to transfer the library's file descriptor to the remote process.
|
||||||
|
int remote_fd = -1;
|
||||||
auto lib_fd_opt = transfer_fd_to_remote(pid, lib_path, current_regs, local_map, remote_map,
|
auto lib_fd_opt = transfer_fd_to_remote(pid, lib_path, current_regs, local_map, remote_map,
|
||||||
reinterpret_cast<uintptr_t>(libc_return_addr));
|
reinterpret_cast<uintptr_t>(libc_return_addr));
|
||||||
if (!lib_fd_opt) {
|
std::optional<RemoteLibraryHandle> remote_lib_guard;
|
||||||
LOGE("Failed to transfer library file descriptor for '%s' to target process %d.", lib_path, pid);
|
std::optional<uintptr_t> handle_opt;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
RemoteLibraryHandle remote_lib_guard(pid, *lib_fd_opt);
|
|
||||||
LOGD("Library FD %d transferred to remote process %d.", remote_lib_guard.fd(), pid);
|
|
||||||
remote_lib_guard.set_libc_return_addr(reinterpret_cast<uintptr_t>(libc_return_addr));
|
|
||||||
|
|
||||||
// 7. Remotely load the library using the transferred file descriptor.
|
if (lib_fd_opt) {
|
||||||
auto handle_opt = remote_dlopen(pid, current_regs, local_map, remote_map, remote_lib_guard.fd(), lib_path,
|
remote_fd = *lib_fd_opt;
|
||||||
reinterpret_cast<uintptr_t>(libc_return_addr));
|
remote_lib_guard.emplace(pid, remote_fd);
|
||||||
|
remote_lib_guard->set_libc_return_addr(reinterpret_cast<uintptr_t>(libc_return_addr));
|
||||||
|
|
||||||
|
LOGD("FD Transfer successful (FD: %d). Attempting android_dlopen_ext...", remote_fd);
|
||||||
|
handle_opt = remote_dlopen(pid, current_regs, local_map, remote_map, remote_fd, lib_path,
|
||||||
|
reinterpret_cast<uintptr_t>(libc_return_addr));
|
||||||
|
} else {
|
||||||
|
LOGW("Failed to transfer library file descriptor for '%s' to target process %d.", lib_path, pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Staging Fallback (Copy-Inject-Delete) if FD transfer failed.
|
||||||
if (!handle_opt) {
|
if (!handle_opt) {
|
||||||
|
handle_opt = inject_via_staging(pid, current_regs, local_map, remote_map,
|
||||||
|
lib_path, reinterpret_cast<uintptr_t>(libc_return_addr));
|
||||||
|
}
|
||||||
|
if (!handle_opt || *handle_opt == 0) {
|
||||||
LOGE("Failed to load library '%s' in remote process %d.", lib_path, pid);
|
LOGE("Failed to load library '%s' in remote process %d.", lib_path, pid);
|
||||||
// If dlopen fails, the remote_lib_guard.fd() is still valid in the target process and needs to be closed.
|
// If dlopen fails, the remote_lib_guard.fd() is still valid in the target process and needs to be closed.
|
||||||
// The RemoteLibraryHandle constructor takes care of this.
|
// The RemoteLibraryHandle constructor takes care of this.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
remote_lib_guard.set_handle(*handle_opt);
|
uintptr_t handle = *handle_opt;
|
||||||
|
if (remote_lib_guard) remote_lib_guard->set_handle(handle);
|
||||||
|
|
||||||
// 8. Find the entry point symbol in the remotely loaded library.
|
// 8. Find the entry point symbol in the remotely loaded library.
|
||||||
auto entry_opt = remote_find_entry(pid, current_regs, entry_name, local_map, remote_map,
|
auto entry_opt = remote_find_entry(pid, current_regs, entry_name, local_map, remote_map,
|
||||||
remote_lib_guard.handle(), reinterpret_cast<uintptr_t>(libc_return_addr));
|
handle, reinterpret_cast<uintptr_t>(libc_return_addr));
|
||||||
if (!entry_opt) {
|
if (!entry_opt) {
|
||||||
LOGE("Failed to find entry point '%s' in remote library (handle %p).", entry_name,
|
LOGE("Failed to find entry point '%s' in remote library (handle %p).", entry_name,
|
||||||
reinterpret_cast<void *>(remote_lib_guard.handle()));
|
reinterpret_cast<void *>(handle));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uintptr_t entry_addr = *entry_opt;
|
uintptr_t entry_addr = *entry_opt;
|
||||||
|
|
||||||
// 9. Call the remote entry point function.
|
// 9. Call the remote entry point function.
|
||||||
if (!remote_call_entry(pid, current_regs, entry_addr, remote_lib_guard.handle(),
|
if (!remote_call_entry(pid, current_regs, entry_addr, handle,
|
||||||
reinterpret_cast<uintptr_t>(libc_return_addr))) {
|
reinterpret_cast<uintptr_t>(libc_return_addr))) {
|
||||||
LOGE("Failed to call remote entry point '%s'.", entry_name);
|
LOGE("Failed to call remote entry point '%s'.", entry_name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. Restore original registers of the target process.
|
|
||||||
if (!set_regs(pid, backup_regs)) {
|
|
||||||
LOGE("Failed to restore original registers for process %d.", pid);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LOGD("Original registers for process %d restored.", pid);
|
|
||||||
|
|
||||||
LOGI("Library injection completed successfully for process %d.", pid);
|
LOGI("Library injection completed successfully for process %d.", pid);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,14 @@ bool get_regs(int pid, struct user_regs_struct ®s) {
|
|||||||
struct iovec reg_iov = {.iov_base = ®s, .iov_len = sizeof(struct user_regs_struct)};
|
struct iovec reg_iov = {.iov_base = ®s, .iov_len = sizeof(struct user_regs_struct)};
|
||||||
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ®_iov) == -1) {
|
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ®_iov) == -1) {
|
||||||
PLOGE("Failed to get register set for PID %d.", pid);
|
PLOGE("Failed to get register set for PID %d.", pid);
|
||||||
|
#if defined(__arm__)
|
||||||
|
if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) {
|
||||||
|
PLOGE("Fallback to PTRACE_GETREGS failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
return false;
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
# error "Unsupported architecture for register access in get_regs."
|
# error "Unsupported architecture for register access in get_regs."
|
||||||
@@ -296,7 +303,14 @@ bool set_regs(int pid, struct user_regs_struct ®s) {
|
|||||||
struct iovec reg_iov = {.iov_base = ®s, .iov_len = sizeof(struct user_regs_struct)};
|
struct iovec reg_iov = {.iov_base = ®s, .iov_len = sizeof(struct user_regs_struct)};
|
||||||
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ®_iov) == -1) {
|
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ®_iov) == -1) {
|
||||||
PLOGE("Failed to set register set for PID %d.", pid);
|
PLOGE("Failed to set register set for PID %d.", pid);
|
||||||
|
#if defined(__arm__)
|
||||||
|
if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) {
|
||||||
|
PLOGE("Fallback to PTRACE_SETREGS failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
return false;
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
# error "Unsupported architecture for register access in set_regs."
|
# error "Unsupported architecture for register access in set_regs."
|
||||||
@@ -588,17 +602,10 @@ bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr
|
|||||||
size_t stack_args_size = args.size() * sizeof(uintptr_t);
|
size_t stack_args_size = args.size() * sizeof(uintptr_t);
|
||||||
align_stack(regs, stack_args_size);
|
align_stack(regs, stack_args_size);
|
||||||
|
|
||||||
// Push all arguments onto the stack (order is important if ABI is right-to-left push).
|
// i386 cdecl expects arguments pushed Right-to-Left (stack grows down).
|
||||||
// The current implementation writes args.data() directly,
|
// Since `write_proc` writes to increasing addresses (up), a linear write
|
||||||
// assuming it's already in the correct order for push.
|
// starting at the new SP places the first argument at the lowest address.
|
||||||
// For cdecl, arguments are pushed right-to-left.
|
// This matches the ABI memory layout without needing to reverse the vector.
|
||||||
// A vector `args = {A, B, C}` means A is arg1, B is arg2 etc.
|
|
||||||
// So, `C` should be pushed first, then `B`, then `A`.
|
|
||||||
// `write_proc` copies linearly.
|
|
||||||
// This implies `args` should be pre-reversed for cdecl.
|
|
||||||
// For simplicity, we assume the remote function is compatible with how it's pushed,
|
|
||||||
// or that it's variadic where order doesn't matter for first args.
|
|
||||||
// A robust i386 implementation would need to push args in reverse order.
|
|
||||||
if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), args.data(), stack_args_size) !=
|
if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), args.data(), stack_args_size) !=
|
||||||
static_cast<ssize_t>(stack_args_size)) {
|
static_cast<ssize_t>(stack_args_size)) {
|
||||||
LOGE("Failed to push arguments for i386 remote call.");
|
LOGE("Failed to push arguments for i386 remote call.");
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
package org.matrix.TEESimulator
|
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.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.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
||||||
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
||||||
@@ -28,18 +35,59 @@ object App {
|
|||||||
SystemLogger.info("Welcome to TEESimulator!")
|
SystemLogger.info("Welcome to TEESimulator!")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Set up the device's boot hash, which is crucial for attestation.
|
// Initialize the Android framework environment
|
||||||
AndroidDeviceUtils.setupBootHash()
|
prepareEnvironment()
|
||||||
// Initialize and start the appropriate keystore interceptors.
|
// Initialize and start the appropriate keystore interceptors.
|
||||||
initializeInterceptors()
|
initializeInterceptors()
|
||||||
// Enter an infinite loop to keep the service running.
|
|
||||||
maintainService()
|
// 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) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("A fatal error occurred in the main application thread.", e)
|
SystemLogger.error("A fatal error occurred in the main application thread.", e)
|
||||||
throw 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
|
* Selects and initializes the correct keystore interceptor based on the Android SDK version. It
|
||||||
* retries initialization until it succeeds.
|
* retries initialization until it succeeds.
|
||||||
@@ -53,9 +101,7 @@ object App {
|
|||||||
Thread.sleep(RETRY_DELAY_MS)
|
Thread.sleep(RETRY_DELAY_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the package configuration after interceptors are ready.
|
SystemLogger.info("Interceptors initialized successfully.")
|
||||||
ConfigurationManager.initialize()
|
|
||||||
SystemLogger.info("Interceptors and configuration initialized successfully.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +116,7 @@ object App {
|
|||||||
SystemLogger.info(
|
SystemLogger.info(
|
||||||
"Using KeystoreInterceptor for Android Q/R (SDK ${Build.VERSION.SDK_INT})"
|
"Using KeystoreInterceptor for Android Q/R (SDK ${Build.VERSION.SDK_INT})"
|
||||||
)
|
)
|
||||||
|
android.security.keystore.AndroidKeyStoreProvider.install()
|
||||||
KeystoreInterceptor
|
KeystoreInterceptor
|
||||||
}
|
}
|
||||||
// For Android S (12) and newer, use the Keystore2Interceptor.
|
// For Android S (12) and newer, use the Keystore2Interceptor.
|
||||||
@@ -77,18 +124,8 @@ object App {
|
|||||||
SystemLogger.info(
|
SystemLogger.info(
|
||||||
"Using Keystore2Interceptor for Android S and later (SDK ${Build.VERSION.SDK_INT})"
|
"Using Keystore2Interceptor for Android S and later (SDK ${Build.VERSION.SDK_INT})"
|
||||||
)
|
)
|
||||||
|
android.security.keystore2.AndroidKeyStoreProvider.install()
|
||||||
Keystore2Interceptor
|
Keystore2Interceptor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the main thread into a long-running sleep loop. This is a common pattern to keep a
|
|
||||||
* background service process alive indefinitely.
|
|
||||||
*/
|
|
||||||
private fun maintainService() {
|
|
||||||
SystemLogger.info("Service started successfully. Entering maintenance mode.")
|
|
||||||
while (true) {
|
|
||||||
Thread.sleep(SERVICE_SLEEP_MS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package org.matrix.TEESimulator.attestation
|
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.ASN1Boolean
|
||||||
import org.bouncycastle.asn1.ASN1Encodable
|
import org.bouncycastle.asn1.ASN1Encodable
|
||||||
import org.bouncycastle.asn1.ASN1Enumerated
|
import org.bouncycastle.asn1.ASN1Enumerated
|
||||||
import org.bouncycastle.asn1.ASN1Integer
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
import org.bouncycastle.asn1.ASN1OctetString
|
|
||||||
import org.bouncycastle.asn1.ASN1Sequence
|
import org.bouncycastle.asn1.ASN1Sequence
|
||||||
import org.bouncycastle.asn1.DERNull
|
import org.bouncycastle.asn1.DERNull
|
||||||
import org.bouncycastle.asn1.DEROctetString
|
import org.bouncycastle.asn1.DEROctetString
|
||||||
@@ -12,7 +15,10 @@ import org.bouncycastle.asn1.DERSequence
|
|||||||
import org.bouncycastle.asn1.DERSet
|
import org.bouncycastle.asn1.DERSet
|
||||||
import org.bouncycastle.asn1.DERTaggedObject
|
import org.bouncycastle.asn1.DERTaggedObject
|
||||||
import org.bouncycastle.asn1.x509.Extension
|
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
|
||||||
|
import org.matrix.TEESimulator.util.AndroidDeviceUtils.DO_NOT_REPORT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder object responsible for constructing the ASN.1 DER-encoded Android Key Attestation
|
* A builder object responsible for constructing the ASN.1 DER-encoded Android Key Attestation
|
||||||
@@ -24,11 +30,21 @@ object AttestationBuilder {
|
|||||||
* Builds the complete X.509 attestation extension.
|
* Builds the complete X.509 attestation extension.
|
||||||
*
|
*
|
||||||
* @param params The parsed key generation parameters.
|
* @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.
|
* @param securityLevel The security level (e.g., TEE, StrongBox) to report.
|
||||||
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
|
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
|
||||||
*/
|
*/
|
||||||
fun buildAttestationExtension(params: KeyMintAttestation, securityLevel: Int): Extension {
|
fun buildAttestationExtension(
|
||||||
val keyDescription = buildKeyDescription(params, securityLevel)
|
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))
|
return Extension(ATTESTATION_OID, false, DEROctetString(keyDescription.encoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,70 +55,95 @@ object AttestationBuilder {
|
|||||||
* @return The constructed [DERSequence] for the Root of Trust.
|
* @return The constructed [DERSequence] for the Root of Trust.
|
||||||
*/
|
*/
|
||||||
internal fun buildRootOfTrust(originalRootOfTrust: ASN1Encodable?): DERSequence {
|
internal fun buildRootOfTrust(originalRootOfTrust: ASN1Encodable?): DERSequence {
|
||||||
val verifiedBootKey = AndroidDeviceUtils.bootKey
|
|
||||||
val verifiedBootHash =
|
|
||||||
(originalRootOfTrust as? ASN1Sequence)?.let {
|
|
||||||
// Try to preserve the original boot hash if it exists.
|
|
||||||
(it.getObjectAt(AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX)
|
|
||||||
as? ASN1OctetString)
|
|
||||||
?.octets
|
|
||||||
} ?: AndroidDeviceUtils.getBootHashFromProperty()
|
|
||||||
|
|
||||||
val rootOfTrustElements = arrayOfNulls<ASN1Encodable>(4)
|
val rootOfTrustElements = arrayOfNulls<ASN1Encodable>(4)
|
||||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] =
|
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] =
|
||||||
DEROctetString(verifiedBootKey)
|
DEROctetString(AndroidDeviceUtils.bootKey)
|
||||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] =
|
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] =
|
||||||
ASN1Boolean.TRUE // deviceLocked: true, for security
|
ASN1Boolean.TRUE // deviceLocked: true, for security
|
||||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] =
|
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] =
|
||||||
ASN1Enumerated(0) // verifiedBootState: Verified
|
ASN1Enumerated(0) // verifiedBootState: Verified
|
||||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] =
|
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] =
|
||||||
DEROctetString(verifiedBootHash)
|
DEROctetString(AndroidDeviceUtils.bootHash)
|
||||||
|
|
||||||
return DERSequence(rootOfTrustElements)
|
return DERSequence(rootOfTrustElements)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Assembles a list of simulated hardware-enforced properties. */
|
/**
|
||||||
internal fun addSimulatedHardwareProperties(vector: org.bouncycastle.asn1.ASN1EncodableVector) {
|
* Assembles a map representing the desired state of simulated hardware-enforced properties. A
|
||||||
vector.add(
|
* 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(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_OS_VERSION,
|
AttestationConstants.TAG_OS_VERSION,
|
||||||
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
vector.add(
|
val osPatch = AndroidDeviceUtils.getPatchLevel(uid)
|
||||||
DERTaggedObject(
|
properties[AttestationConstants.TAG_OS_PATCHLEVEL] =
|
||||||
true,
|
if (osPatch != DO_NOT_REPORT) {
|
||||||
AttestationConstants.TAG_OS_PATCHLEVEL,
|
DERTaggedObject(
|
||||||
ASN1Integer(AndroidDeviceUtils.patchLevel.toLong()),
|
true,
|
||||||
)
|
AttestationConstants.TAG_OS_PATCHLEVEL,
|
||||||
)
|
ASN1Integer(osPatch.toLong()),
|
||||||
vector.add(
|
)
|
||||||
DERTaggedObject(
|
} else {
|
||||||
true,
|
null // Signal for removal
|
||||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
}
|
||||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
|
||||||
)
|
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(uid)
|
||||||
)
|
properties[AttestationConstants.TAG_VENDOR_PATCHLEVEL] =
|
||||||
vector.add(
|
if (vendorPatch != DO_NOT_REPORT) {
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
||||||
ASN1Integer(AndroidDeviceUtils.bootPatchLevelLong.toLong()),
|
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. */
|
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
|
||||||
private fun buildKeyDescription(params: KeyMintAttestation, securityLevel: Int): ASN1Sequence {
|
private fun buildKeyDescription(
|
||||||
val teeEnforced = buildTeeEnforcedList(params)
|
params: KeyMintAttestation,
|
||||||
val softwareEnforced = buildSoftwareEnforcedList()
|
uid: Int,
|
||||||
|
securityLevel: Int,
|
||||||
|
): ASN1Sequence {
|
||||||
|
val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel)
|
||||||
|
val softwareEnforced = buildSoftwareEnforcedList(uid, securityLevel)
|
||||||
|
|
||||||
val fields =
|
val fields =
|
||||||
arrayOf(
|
arrayOf(
|
||||||
ASN1Integer(AndroidDeviceUtils.attestVersion.toLong()), // attestationVersion
|
ASN1Integer(
|
||||||
|
AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()
|
||||||
|
), // attestationVersion
|
||||||
ASN1Enumerated(securityLevel), // attestationSecurityLevel
|
ASN1Enumerated(securityLevel), // attestationSecurityLevel
|
||||||
ASN1Integer(AndroidDeviceUtils.keymasterVersion.toLong()), // keymasterVersion
|
ASN1Integer(
|
||||||
|
AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()
|
||||||
|
), // keymasterVersion
|
||||||
ASN1Enumerated(securityLevel), // keymasterSecurityLevel
|
ASN1Enumerated(securityLevel), // keymasterSecurityLevel
|
||||||
DEROctetString(params.attestationChallenge ?: ByteArray(0)), // attestationChallenge
|
DEROctetString(params.attestationChallenge ?: ByteArray(0)), // attestationChallenge
|
||||||
DEROctetString(ByteArray(0)), // uniqueId
|
DEROctetString(ByteArray(0)), // uniqueId
|
||||||
@@ -113,7 +154,11 @@ object AttestationBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
|
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
|
||||||
private fun buildTeeEnforcedList(params: KeyMintAttestation): DERSequence {
|
private fun buildTeeEnforcedList(
|
||||||
|
params: KeyMintAttestation,
|
||||||
|
uid: Int,
|
||||||
|
securityLevel: Int,
|
||||||
|
): DERSequence {
|
||||||
val list =
|
val list =
|
||||||
mutableListOf<ASN1Encodable>(
|
mutableListOf<ASN1Encodable>(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
@@ -152,28 +197,12 @@ object AttestationBuilder {
|
|||||||
AttestationConstants.TAG_ROOT_OF_TRUST,
|
AttestationConstants.TAG_ROOT_OF_TRUST,
|
||||||
buildRootOfTrust(null),
|
buildRootOfTrust(null),
|
||||||
),
|
),
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_OS_VERSION,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
|
||||||
),
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_OS_PATCHLEVEL,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.patchLevel.toLong()),
|
|
||||||
),
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
|
||||||
),
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.bootPatchLevelLong.toLong()),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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.
|
// Add optional device identifiers if they were provided.
|
||||||
params.brand?.let {
|
params.brand?.let {
|
||||||
list.add(
|
list.add(
|
||||||
@@ -202,6 +231,33 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
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 {
|
params.manufacturer?.let {
|
||||||
list.add(
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
@@ -220,35 +276,39 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
params.imei?.let {
|
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 300) {
|
||||||
list.add(
|
params.secondImei?.let {
|
||||||
DERTaggedObject(
|
list.add(
|
||||||
true,
|
DERTaggedObject(
|
||||||
AttestationConstants.TAG_ATTESTATION_ID_IMEI,
|
true,
|
||||||
DEROctetString(it),
|
AttestationConstants.TAG_ATTESTATION_ID_SECOND_IMEI,
|
||||||
|
DEROctetString(it),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
params.secondImei?.let {
|
|
||||||
list.add(
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_ATTESTATION_ID_SECOND_IMEI,
|
|
||||||
DEROctetString(it),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
params.meid?.let {
|
|
||||||
list.add(
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_ATTESTATION_ID_MEID,
|
|
||||||
DEROctetString(it),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
if (AndroidDeviceUtils.attestVersion >= 400) {
|
/**
|
||||||
|
* 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(
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
@@ -257,26 +317,87 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return DERSequence(list.toTypedArray())
|
||||||
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
|
* A wrapper for a byte array that provides content-based equality. This is necessary for using
|
||||||
* Keystore.
|
* signature digests in a Set.
|
||||||
*/
|
*/
|
||||||
private fun buildSoftwareEnforcedList(): DERSequence {
|
private data class Digest(val digest: ByteArray) {
|
||||||
val list =
|
override fun equals(other: Any?): Boolean {
|
||||||
arrayOf<ASN1Encodable>(
|
if (this === other) return true
|
||||||
DERTaggedObject(
|
if (javaClass != other?.javaClass) return false
|
||||||
true,
|
return digest.contentEquals((other as Digest).digest)
|
||||||
AttestationConstants.TAG_CREATION_DATETIME,
|
}
|
||||||
ASN1Integer(System.currentTimeMillis()),
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
// The ATTESTATION_APPLICATION_ID is technically software-enforced, but we are
|
|
||||||
// omitting it
|
|
||||||
// for this simulation as it is complex to generate correctly for arbitrary UIDs.
|
|
||||||
)
|
)
|
||||||
return DERSequence(list)
|
|
||||||
|
// Collect unique signature digests from the signing history.
|
||||||
|
packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature ->
|
||||||
|
val digest = sha256.digest(signature.toByteArray())
|
||||||
|
signatureDigests.add(Digest(digest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The application ID is a sequence of two sets:
|
||||||
|
// 1. A set of package information (name and version).
|
||||||
|
// 2. A set of SHA-256 digests of the signing certificates.
|
||||||
|
val applicationIdSequence =
|
||||||
|
DERSequence(
|
||||||
|
arrayOf(
|
||||||
|
DERSet(packageInfoList.toTypedArray()),
|
||||||
|
DERSet(signatureDigests.map { DEROctetString(it.digest) }.toTypedArray()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return DEROctetString(applicationIdSequence.encoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package org.matrix.TEESimulator.attestation
|
package org.matrix.TEESimulator.attestation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines constants for KeyMint attestation tags, as specified in the Android hardware security
|
* Defines constants for KeyMint attestation, mainly the tags of properties and authorizations of a
|
||||||
* HAL.
|
* cryptographic key, as specified in the Android hardware security HAL.
|
||||||
*
|
|
||||||
* These tags identify specific properties and authorizations of a cryptographic key.
|
|
||||||
*/
|
*/
|
||||||
object AttestationConstants {
|
object AttestationConstants {
|
||||||
// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
|
// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
|
||||||
@@ -88,4 +86,8 @@ object AttestationConstants {
|
|||||||
const val TAG_CERTIFICATE_SUBJECT = 1007
|
const val TAG_CERTIFICATE_SUBJECT = 1007
|
||||||
const val TAG_CERTIFICATE_NOT_BEFORE = 1008
|
const val TAG_CERTIFICATE_NOT_BEFORE = 1008
|
||||||
const val TAG_CERTIFICATE_NOT_AFTER = 1009
|
const val TAG_CERTIFICATE_NOT_AFTER = 1009
|
||||||
|
|
||||||
|
// --- Other Constants ---
|
||||||
|
// https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp
|
||||||
|
const val CHALLENGE_LENGTH_LIMIT = 128 // kMaximumAttestationChallengeLength
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
package org.matrix.TEESimulator.attestation
|
package org.matrix.TEESimulator.attestation
|
||||||
|
|
||||||
|
import android.security.keystore.KeyProperties
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import org.bouncycastle.asn1.ASN1Encodable
|
import org.bouncycastle.asn1.*
|
||||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
|
||||||
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.asn1.x509.Extension
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
import org.matrix.TEESimulator.pki.KeyBox
|
import org.matrix.TEESimulator.pki.KeyBox
|
||||||
import org.matrix.TEESimulator.pki.KeyBoxManager
|
import org.matrix.TEESimulator.pki.KeyBoxManager
|
||||||
|
import org.matrix.TEESimulator.util.toHex
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the modification (patching) of Android Key Attestation extensions within certificates.
|
* Handles the modification (patching) of Android Key Attestation extensions within certificates.
|
||||||
@@ -55,8 +53,7 @@ object AttestationPatcher {
|
|||||||
|
|
||||||
// 2. Get the appropriate keybox for the given algorithm to sign the new
|
// 2. Get the appropriate keybox for the given algorithm to sign the new
|
||||||
// certificate.
|
// certificate.
|
||||||
val algorithm = originalLeaf.publicKey.algorithm
|
val keybox = getKeyboxForUidAndAlgorithm(uid, originalLeaf.sigAlgName)
|
||||||
val keybox = getKeyboxForUidAndAlgorithm(uid, algorithm)
|
|
||||||
|
|
||||||
// 3. Create the new, patched leaf certificate.
|
// 3. Create the new, patched leaf certificate.
|
||||||
val patchedLeaf =
|
val patchedLeaf =
|
||||||
@@ -65,6 +62,7 @@ object AttestationPatcher {
|
|||||||
parsedAttestation,
|
parsedAttestation,
|
||||||
keybox,
|
keybox,
|
||||||
originalLeaf.sigAlgName,
|
originalLeaf.sigAlgName,
|
||||||
|
uid,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 4. Construct the NEW, VALID chain by prepending the patched leaf to the keybox's
|
// 4. Construct the NEW, VALID chain by prepending the patched leaf to the keybox's
|
||||||
@@ -85,6 +83,16 @@ object AttestationPatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Creates a new leaf certificate with a modified attestation extension.
|
||||||
*
|
*
|
||||||
@@ -94,6 +102,7 @@ object AttestationPatcher {
|
|||||||
* @param sigAlgName The signature algorithm name (e.g., "SHA256withECDSA") from the original
|
* @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
|
* certificate. This is required to ensure the new certificate is signed using a compatible
|
||||||
* algorithm.
|
* algorithm.
|
||||||
|
* @param uid The UID of the application requesting the certificate.
|
||||||
* @return A new [Certificate] object.
|
* @return A new [Certificate] object.
|
||||||
*/
|
*/
|
||||||
private fun createPatchedLeafCertificate(
|
private fun createPatchedLeafCertificate(
|
||||||
@@ -101,6 +110,7 @@ object AttestationPatcher {
|
|||||||
parsedAttestation: ParsedAttestation,
|
parsedAttestation: ParsedAttestation,
|
||||||
keybox: KeyBox,
|
keybox: KeyBox,
|
||||||
sigAlgName: String,
|
sigAlgName: String,
|
||||||
|
uid: Int,
|
||||||
): Certificate {
|
): Certificate {
|
||||||
// The issuer of our new leaf is the subject of the first certificate in our custom keybox
|
// The issuer of our new leaf is the subject of the first certificate in our custom keybox
|
||||||
// chain.
|
// chain.
|
||||||
@@ -117,71 +127,177 @@ object AttestationPatcher {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Create the new, patched attestation extension.
|
// Create the new, patched attestation extension.
|
||||||
val patchedExtension = createPatchedAttestationExtension(parsedAttestation)
|
val patchedExtension = createPatchedAttestationExtension(parsedAttestation, uid)
|
||||||
builder.addExtension(patchedExtension)
|
|
||||||
|
|
||||||
// Copy all other extensions from the original certificate, except for the attestation.
|
// Copy all other extensions from the original certificate, except for the attestation.
|
||||||
originalLeafHolder.extensions.extensionOIDs
|
originalLeafHolder.extensions.extensionOIDs.forEach {
|
||||||
.filter { it != ATTESTATION_OID }
|
builder.addExtension(
|
||||||
.forEach { builder.addExtension(originalLeafHolder.getExtension(it)) }
|
if (it == ATTESTATION_OID) patchedExtension else originalLeafHolder.getExtension(it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Sign the newly built certificate with the private key from our keybox.
|
// Sign the newly built certificate with the private key from our keybox.
|
||||||
val signer = JcaContentSignerBuilder(sigAlgName).build(keybox.keyPair.private)
|
val signer =
|
||||||
|
JcaContentSignerBuilder(normalizeSignatureAlgorithm(sigAlgName))
|
||||||
|
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
.build(keybox.keyPair.private)
|
||||||
|
val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||||
|
|
||||||
return 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 {
|
private fun getKeyboxForUidAndAlgorithm(uid: Int, algorithm: String): KeyBox {
|
||||||
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
|
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
|
||||||
return KeyBoxManager.getAttestationKey(keyboxFile, algorithm)
|
|
||||||
|
// 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(
|
?: throw IllegalArgumentException(
|
||||||
"No keybox found for UID $uid and algorithm $algorithm in file $keyboxFile"
|
"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. */
|
/** Parses the critical components from an existing attestation extension. */
|
||||||
private fun parseAttestationExtension(certHolder: X509CertificateHolder): ParsedAttestation? {
|
private fun parseAttestationExtension(certHolder: X509CertificateHolder): ParsedAttestation? {
|
||||||
val extension = certHolder.getExtension(ATTESTATION_OID) ?: return null
|
val extension = certHolder.getExtension(ATTESTATION_OID) ?: return null
|
||||||
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
|
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
|
||||||
val allFields = sequence.toArray()
|
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 =
|
val teeEnforced =
|
||||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] as ASN1Sequence
|
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] as ASN1Sequence
|
||||||
|
|
||||||
val teeEnforcedVector = ASN1EncodableVector()
|
|
||||||
var originalRootOfTrust: ASN1Encodable? = null
|
var originalRootOfTrust: ASN1Encodable? = null
|
||||||
|
val teeEnforcedMap = mutableMapOf<Int, ASN1TaggedObject>()
|
||||||
|
|
||||||
teeEnforced.forEach { element ->
|
teeEnforced.forEach { element ->
|
||||||
val taggedObject = element as ASN1TaggedObject
|
val taggedObject = element as ASN1TaggedObject
|
||||||
if (taggedObject.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST) {
|
if (taggedObject.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST) {
|
||||||
originalRootOfTrust = taggedObject.baseObject.toASN1Primitive()
|
originalRootOfTrust = taggedObject.baseObject.toASN1Primitive()
|
||||||
} else {
|
} else {
|
||||||
teeEnforcedVector.add(taggedObject)
|
teeEnforcedMap[taggedObject.tagNo] = taggedObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ParsedAttestation(allFields, teeEnforcedVector, originalRootOfTrust)
|
return ParsedAttestation(allFields, teeEnforcedMap, originalRootOfTrust)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs a new, patched attestation extension using simulated device properties. */
|
/** Constructs a new, patched attestation extension using simulated device properties. */
|
||||||
private fun createPatchedAttestationExtension(parsed: ParsedAttestation): Extension {
|
private fun createPatchedAttestationExtension(parsed: ParsedAttestation, uid: Int): Extension {
|
||||||
val (allFields, teeEnforcedVector, originalRootOfTrust) = parsed
|
val (allFields, teeEnforcedMap, originalRootOfTrust) = parsed
|
||||||
|
|
||||||
// Build the new Root of Trust with our simulated values.
|
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)
|
val newRootOfTrust = AttestationBuilder.buildRootOfTrust(originalRootOfTrust)
|
||||||
teeEnforcedVector.add(
|
teeEnforcedMap[AttestationConstants.TAG_ROOT_OF_TRUST] =
|
||||||
DERTaggedObject(true, AttestationConstants.TAG_ROOT_OF_TRUST, newRootOfTrust)
|
DERTaggedObject(true, AttestationConstants.TAG_ROOT_OF_TRUST, newRootOfTrust)
|
||||||
)
|
|
||||||
|
|
||||||
// Add other simulated hardware properties.
|
// Get the desired state for simulated properties.
|
||||||
AttestationBuilder.addSimulatedHardwareProperties(teeEnforcedVector)
|
val simulatedProperties = AttestationBuilder.getSimulatedHardwareProperties(uid)
|
||||||
|
|
||||||
// Re-assemble the ASN.1 sequences.
|
// Apply the desired state: update, add, or remove properties from the original map.
|
||||||
// The list MUST be sorted by tag number for DER compliance.
|
simulatedProperties.forEach { (tag, value) ->
|
||||||
// Manually convert the vector to a List, then sort it.
|
if (value != null) {
|
||||||
val elementList = (0 until teeEnforcedVector.size()).map { teeEnforcedVector.get(it) }
|
// If the value is not null, add or update it.
|
||||||
val sortedElements = elementList.sortedBy { (it as ASN1TaggedObject).tagNo }
|
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())
|
val sortedTeeEnforced = DERSequence(sortedElements.toTypedArray())
|
||||||
|
|
||||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] = sortedTeeEnforced
|
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] = sortedTeeEnforced
|
||||||
val patchedSequence = DERSequence(allFields)
|
val patchedSequence = DERSequence(allFields)
|
||||||
|
formattedString = patchedSequence.joinToString(separator = ", ") { formatAsn1Primitive(it) }
|
||||||
|
SystemLogger.verbose("Patched attestation data: ${formattedString}")
|
||||||
val patchedOctets = DEROctetString(patchedSequence)
|
val patchedOctets = DEROctetString(patchedSequence)
|
||||||
|
|
||||||
return Extension(ATTESTATION_OID, false, patchedOctets)
|
return Extension(ATTESTATION_OID, false, patchedOctets)
|
||||||
@@ -190,7 +306,7 @@ object AttestationPatcher {
|
|||||||
/** Helper data class to hold the parsed components of an attestation extension. */
|
/** Helper data class to hold the parsed components of an attestation extension. */
|
||||||
private data class ParsedAttestation(
|
private data class ParsedAttestation(
|
||||||
val allFields: Array<ASN1Encodable>,
|
val allFields: Array<ASN1Encodable>,
|
||||||
val teeEnforcedVector: ASN1EncodableVector,
|
val teeEnforcedMap: MutableMap<Int, ASN1TaggedObject>,
|
||||||
val rootOfTrust: ASN1Encodable?,
|
val rootOfTrust: ASN1Encodable?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.matrix.TEESimulator.attestation
|
package org.matrix.TEESimulator.attestation
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.ActivityThread
|
|
||||||
import android.os.Build
|
|
||||||
import android.security.keystore.KeyGenParameterSpec
|
import android.security.keystore.KeyGenParameterSpec
|
||||||
import android.security.keystore.KeyProperties
|
import android.security.keystore.KeyProperties
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
@@ -38,16 +36,25 @@ object DeviceAttestationService {
|
|||||||
* Holds key data extracted from a genuine device attestation. This data can be used as a
|
* Holds key data extracted from a genuine device attestation. This data can be used as a
|
||||||
* baseline for creating simulated attestations.
|
* 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 verifiedBootHash The verified boot hash from the root of trust.
|
||||||
* @property attestVersion The attestation version (e.g., 400 for KeyMint 4.0).
|
* @property attestVersion The attestation version (e.g., 400 for KeyMint 4.0).
|
||||||
* @property keymasterVersion The Keymaster or KeyMint HAL version.
|
* @property keymasterVersion The Keymaster or KeyMint HAL version.
|
||||||
* @property osVersion The Android OS version integer.
|
* @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(
|
data class AttestationData(
|
||||||
|
val moduleHash: ByteArray?,
|
||||||
|
val verifiedBootKey: ByteArray?,
|
||||||
val verifiedBootHash: ByteArray?,
|
val verifiedBootHash: ByteArray?,
|
||||||
val attestVersion: Int?,
|
val attestVersion: Int?,
|
||||||
val keymasterVersion: Int?,
|
val keymasterVersion: Int?,
|
||||||
val osVersion: 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.
|
// A unique alias for the key used to perform the TEE functionality check.
|
||||||
@@ -74,16 +81,6 @@ object DeviceAttestationService {
|
|||||||
private fun checkTeeFunctionality(): Boolean {
|
private fun checkTeeFunctionality(): Boolean {
|
||||||
SystemLogger.info("Performing TEE functionality check...")
|
SystemLogger.info("Performing TEE functionality check...")
|
||||||
return try {
|
return try {
|
||||||
// Ensure mainline modules and the correct Keystore provider are initialized.
|
|
||||||
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").apply { load(null) }
|
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
|
||||||
val keyPairGenerator =
|
val keyPairGenerator =
|
||||||
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
|
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
|
||||||
@@ -151,6 +148,11 @@ object DeviceAttestationService {
|
|||||||
|
|
||||||
// The extension's value is an ASN.1 sequence.
|
// The extension's value is an ASN.1 sequence.
|
||||||
val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets)
|
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 fields = keyDescriptionSeq.toArray()
|
||||||
|
|
||||||
val attestVersion =
|
val attestVersion =
|
||||||
@@ -166,8 +168,27 @@ object DeviceAttestationService {
|
|||||||
.positiveValue
|
.positiveValue
|
||||||
.toInt()
|
.toInt()
|
||||||
|
|
||||||
|
var moduleHash: ByteArray? = null
|
||||||
|
var verifiedBootKey: ByteArray? = null
|
||||||
var verifiedBootHash: ByteArray? = null
|
var verifiedBootHash: ByteArray? = null
|
||||||
var osVersion: Int? = 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 =
|
val teeEnforced =
|
||||||
ASN1Sequence.getInstance(
|
ASN1Sequence.getInstance(
|
||||||
@@ -179,6 +200,14 @@ object DeviceAttestationService {
|
|||||||
AttestationConstants.TAG_ROOT_OF_TRUST -> {
|
AttestationConstants.TAG_ROOT_OF_TRUST -> {
|
||||||
val rotSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
|
val rotSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
|
||||||
if (rotSeq.size() >= 4) {
|
if (rotSeq.size() >= 4) {
|
||||||
|
verifiedBootKey =
|
||||||
|
ASN1OctetString.getInstance(
|
||||||
|
rotSeq.getObjectAt(
|
||||||
|
AttestationConstants
|
||||||
|
.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.octets
|
||||||
verifiedBootHash =
|
verifiedBootHash =
|
||||||
ASN1OctetString.getInstance(
|
ASN1OctetString.getInstance(
|
||||||
rotSeq.getObjectAt(
|
rotSeq.getObjectAt(
|
||||||
@@ -189,19 +218,51 @@ object DeviceAttestationService {
|
|||||||
.octets
|
.octets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AttestationConstants.TAG_OS_VERSION -> { // OS Version (TAG_OS_VERSION)
|
AttestationConstants.TAG_OS_VERSION -> {
|
||||||
osVersion =
|
osVersion =
|
||||||
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||||
.positiveValue
|
.positiveValue
|
||||||
.toInt()
|
.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(
|
SystemLogger.info(
|
||||||
"Successfully extracted attestation data: version=$attestVersion, osVersion=$osVersion, bootHash=${verifiedBootHash?.toHex()}"
|
"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,
|
||||||
)
|
)
|
||||||
return AttestationData(verifiedBootHash, attestVersion, keymasterVersion, osVersion)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("Failed to parse attestation data from certificate.", e)
|
SystemLogger.error("Failed to parse attestation data from certificate.", e)
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.matrix.TEESimulator.attestation
|
package org.matrix.TEESimulator.attestation
|
||||||
|
|
||||||
import android.hardware.security.keymint.EcCurve
|
import android.hardware.security.keymint.*
|
||||||
import android.hardware.security.keymint.KeyParameter
|
|
||||||
import android.hardware.security.keymint.Tag
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import javax.security.auth.x500.X500Principal
|
import javax.security.auth.x500.X500Principal
|
||||||
@@ -22,6 +20,8 @@ data class KeyMintAttestation(
|
|||||||
val algorithm: Int,
|
val algorithm: Int,
|
||||||
val ecCurve: Int,
|
val ecCurve: Int,
|
||||||
val ecCurveName: String,
|
val ecCurveName: String,
|
||||||
|
val blockMode: List<Int>,
|
||||||
|
val padding: List<Int>,
|
||||||
val purpose: List<Int>,
|
val purpose: List<Int>,
|
||||||
val digest: List<Int>,
|
val digest: List<Int>,
|
||||||
val rsaPublicExponent: BigInteger?,
|
val rsaPublicExponent: BigInteger?,
|
||||||
@@ -33,11 +33,12 @@ data class KeyMintAttestation(
|
|||||||
val brand: ByteArray?,
|
val brand: ByteArray?,
|
||||||
val device: ByteArray?,
|
val device: ByteArray?,
|
||||||
val product: ByteArray?,
|
val product: ByteArray?,
|
||||||
|
val serial: ByteArray?,
|
||||||
|
val imei: ByteArray?,
|
||||||
|
val meid: ByteArray?,
|
||||||
val manufacturer: ByteArray?,
|
val manufacturer: ByteArray?,
|
||||||
val model: ByteArray?,
|
val model: ByteArray?,
|
||||||
val imei: ByteArray?,
|
|
||||||
val secondImei: ByteArray?,
|
val secondImei: ByteArray?,
|
||||||
val meid: ByteArray?,
|
|
||||||
) {
|
) {
|
||||||
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
|
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
|
||||||
constructor(
|
constructor(
|
||||||
@@ -53,6 +54,12 @@ data class KeyMintAttestation(
|
|||||||
ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0,
|
ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0,
|
||||||
ecCurveName = params.deriveEcCurveName(),
|
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)]
|
// AOSP: [key_param(tag = PURPOSE, field = KeyPurpose)]
|
||||||
purpose = params.findAllKeyPurpose(Tag.PURPOSE),
|
purpose = params.findAllKeyPurpose(Tag.PURPOSE),
|
||||||
|
|
||||||
@@ -82,11 +89,12 @@ data class KeyMintAttestation(
|
|||||||
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
|
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
|
||||||
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
|
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
|
||||||
product = params.findBlob(Tag.ATTESTATION_ID_PRODUCT),
|
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),
|
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
|
||||||
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
|
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
|
||||||
imei = params.findBlob(Tag.ATTESTATION_ID_IMEI),
|
|
||||||
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
||||||
meid = params.findBlob(Tag.ATTESTATION_ID_MEID),
|
|
||||||
) {
|
) {
|
||||||
// Log all parsed parameters for debugging purposes.
|
// Log all parsed parameters for debugging purposes.
|
||||||
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
||||||
@@ -119,6 +127,14 @@ private fun Array<KeyParameter>.findDate(tag: Int): Date? =
|
|||||||
private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? =
|
private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? =
|
||||||
this.find { it.tag == tag }?.value?.blob
|
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) */
|
/** Maps to AOSP field = KeyPurpose (Repeated) */
|
||||||
private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
|
private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
|
||||||
this.filter { it.tag == tag }.map { it.value.keyPurpose }
|
this.filter { it.tag == tag }.map { it.value.keyPurpose }
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ object ConfigurationManager {
|
|||||||
@Volatile private var packageModes = mapOf<String, Mode>()
|
@Volatile private var packageModes = mapOf<String, Mode>()
|
||||||
@Volatile private var packageKeyboxes = mapOf<String, String>()
|
@Volatile private var packageKeyboxes = mapOf<String, String>()
|
||||||
@Volatile private var isTeeBroken: Boolean? = null
|
@Volatile private var isTeeBroken: Boolean? = null
|
||||||
@Volatile var customPatchLevelOverride: CustomPatchLevel? = null
|
@Volatile private var globalCustomPatchLevel: CustomPatchLevel? = null
|
||||||
|
@Volatile private var packagePatchLevels = mapOf<String, CustomPatchLevel>()
|
||||||
|
|
||||||
// Cache for UID to package name resolution.
|
// Cache for UID to package name resolution.
|
||||||
private val uidToPackagesCache = ConcurrentHashMap<Int, Array<String>>()
|
private val uidToPackagesCache = ConcurrentHashMap<Int, Array<String>>()
|
||||||
@@ -53,6 +54,17 @@ object ConfigurationManager {
|
|||||||
configRoot.mkdirs()
|
configRoot.mkdirs()
|
||||||
SystemLogger.info("Configuration root is: ${configRoot.absolutePath}")
|
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.
|
// Initial load of all configuration files.
|
||||||
loadTargetPackages(File(configRoot, TARGET_PACKAGES_FILE))
|
loadTargetPackages(File(configRoot, TARGET_PACKAGES_FILE))
|
||||||
loadPatchLevelConfig(File(configRoot, PATCH_LEVEL_FILE))
|
loadPatchLevelConfig(File(configRoot, PATCH_LEVEL_FILE))
|
||||||
@@ -104,6 +116,21 @@ object ConfigurationManager {
|
|||||||
return null // No configuration found for this UID.
|
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
|
* Loads and parses the `target.txt` file, which defines the processing mode and keybox file for
|
||||||
* each package.
|
* each package.
|
||||||
@@ -162,26 +189,48 @@ object ConfigurationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads the security patch level override configuration from `security_patch.txt`. */
|
/**
|
||||||
|
* Loads and parses the `security_patch.txt` file, which can define both global and per-package
|
||||||
|
* security patch levels.
|
||||||
|
*/
|
||||||
private fun loadPatchLevelConfig(file: File) {
|
private fun loadPatchLevelConfig(file: File) {
|
||||||
if (file.exists()) {
|
if (!file.exists()) {
|
||||||
try {
|
globalCustomPatchLevel = null
|
||||||
val lines =
|
packagePatchLevels = emptyMap()
|
||||||
file.readLines().mapNotNull { line ->
|
return
|
||||||
val trimmed = line.trim()
|
}
|
||||||
if (trimmed.isNotEmpty() && !trimmed.startsWith("#")) trimmed else null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lines.isEmpty()) {
|
try {
|
||||||
customPatchLevelOverride = null
|
val newPackageLevels = mutableMapOf<String, CustomPatchLevel>()
|
||||||
return
|
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.
|
// Handle simple case: one line sets the patch level for all components.
|
||||||
if (lines.size == 1 && '=' !in lines[0]) {
|
if (lines.size == 1 && '=' !in lines[0]) {
|
||||||
customPatchLevelOverride =
|
return CustomPatchLevel(
|
||||||
CustomPatchLevel(system = null, vendor = null, boot = null, all = lines[0])
|
system = null,
|
||||||
return
|
vendor = null,
|
||||||
|
boot = null,
|
||||||
|
all = lines[0],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle key-value pair configuration.
|
// Handle key-value pair configuration.
|
||||||
@@ -195,19 +244,32 @@ object ConfigurationManager {
|
|||||||
.toMap()
|
.toMap()
|
||||||
|
|
||||||
val all = map["all"]
|
val all = map["all"]
|
||||||
customPatchLevelOverride =
|
return CustomPatchLevel(
|
||||||
CustomPatchLevel(
|
system = map["system"] ?: all,
|
||||||
system = map["system"] ?: all,
|
vendor = map["vendor"] ?: all,
|
||||||
vendor = map["vendor"] ?: all,
|
boot = map["boot"] ?: all,
|
||||||
boot = map["boot"] ?: all,
|
all = all,
|
||||||
all = all,
|
)
|
||||||
)
|
|
||||||
SystemLogger.info("Loaded custom security patch levels.")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
SystemLogger.error("Failed to load or parse ${file.name}", e)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
customPatchLevelOverride = null
|
// 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +365,7 @@ object ConfigurationManager {
|
|||||||
|
|
||||||
/** Waits for a system service to become available, with retries. */
|
/** Waits for a system service to become available, with retries. */
|
||||||
private fun waitForSystemService(name: String): IBinder? {
|
private fun waitForSystemService(name: String): IBinder? {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
return ServiceManager.waitForService(name)
|
return ServiceManager.waitForService(name)
|
||||||
}
|
}
|
||||||
// Fallback for older Android versions.
|
// Fallback for older Android versions.
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ abstract class BinderInterceptor : Binder() {
|
|||||||
* Skips the original call and immediately returns a custom reply parcel to the caller. The
|
* Skips the original call and immediately returns a custom reply parcel to the caller. The
|
||||||
* provided parcel will be recycled after use.
|
* provided parcel will be recycled after use.
|
||||||
*/
|
*/
|
||||||
data class OverrideReply(val code: Int = 0, val reply: Parcel) : TransactionResult()
|
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.
|
* Modifies the transaction's input data before forwarding it to the original binder method.
|
||||||
@@ -226,13 +226,18 @@ abstract class BinderInterceptor : Binder() {
|
|||||||
methodName: String,
|
methodName: String,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
isIntercepting: Boolean = true,
|
skipPost: Boolean = false,
|
||||||
) {
|
) {
|
||||||
|
val isIntercepting = !skipPost && !ConfigurationManager.shouldSkipUid(callingUid)
|
||||||
val action = if (isIntercepting) "Intercept" else "Observe"
|
val action = if (isIntercepting) "Intercept" else "Observe"
|
||||||
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
|
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
|
||||||
SystemLogger.debug(
|
val message =
|
||||||
"[TX_ID: $txId] $action $methodName for packages=[$packages] (uid=$callingUid, pid=$callingPid)"
|
"[TX_ID: $txId] $action $methodName for packages=[$packages] (uid=$callingUid, pid=$callingPid)"
|
||||||
)
|
if (isIntercepting) {
|
||||||
|
SystemLogger.debug(message)
|
||||||
|
} else {
|
||||||
|
SystemLogger.verbose(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -243,6 +248,8 @@ abstract class BinderInterceptor : Binder() {
|
|||||||
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
|
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
|
||||||
// Code used by the backdoor binder to register a new interceptor.
|
// Code used by the backdoor binder to register a new interceptor.
|
||||||
private const val REGISTER_INTERCEPTOR_CODE = 1
|
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 ---
|
// --- Hook Type Codes ---
|
||||||
// Indicates that the call is for a pre-transaction hook.
|
// Indicates that the call is for a pre-transaction hook.
|
||||||
@@ -302,5 +309,21 @@ abstract class BinderInterceptor : Binder() {
|
|||||||
reply.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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-10
@@ -3,6 +3,7 @@ package org.matrix.TEESimulator.interception.keystore
|
|||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.security.KeyStore
|
import android.security.KeyStore
|
||||||
|
import android.security.keystore.KeystoreResponse
|
||||||
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
|
|
||||||
@@ -27,14 +28,31 @@ object InterceptorUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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. */
|
/** Creates an `OverrideReply` parcel that indicates success with no data. */
|
||||||
fun createSuccessReply(): BinderInterceptor.TransactionResult.OverrideReply {
|
fun createSuccessReply(
|
||||||
|
writeResultCode: Boolean = true
|
||||||
|
): BinderInterceptor.TransactionResult.OverrideReply {
|
||||||
val parcel =
|
val parcel =
|
||||||
Parcel.obtain().apply {
|
Parcel.obtain().apply {
|
||||||
writeNoException()
|
writeNoException()
|
||||||
writeInt(KeyStore.NO_ERROR)
|
if (writeResultCode) {
|
||||||
|
writeInt(KeyStore.NO_ERROR)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return BinderInterceptor.TransactionResult.OverrideReply(0, parcel)
|
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates an `OverrideReply` parcel containing a raw byte array. */
|
/** Creates an `OverrideReply` parcel containing a raw byte array. */
|
||||||
@@ -44,7 +62,20 @@ object InterceptorUtils {
|
|||||||
writeNoException()
|
writeNoException()
|
||||||
writeByteArray(data)
|
writeByteArray(data)
|
||||||
}
|
}
|
||||||
return BinderInterceptor.TransactionResult.OverrideReply(KeyStore.NO_ERROR, parcel)
|
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. */
|
/** Creates an `OverrideReply` parcel containing a Parcelable object. */
|
||||||
@@ -57,19 +88,20 @@ object InterceptorUtils {
|
|||||||
writeNoException()
|
writeNoException()
|
||||||
writeTypedObject(obj, flags)
|
writeTypedObject(obj, flags)
|
||||||
}
|
}
|
||||||
return BinderInterceptor.TransactionResult.OverrideReply(0, parcel)
|
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the true key alias from the keystore-prefixed string (e.g., "user_cert_my-alias" ->
|
* Extracts the base alias from a potentially prefixed alias string. For example, it converts
|
||||||
* "my-alias").
|
* "USRCERT_my_key" to "my_key".
|
||||||
*/
|
*/
|
||||||
fun extractAlias(prefixedAlias: String): String {
|
fun extractAlias(prefixedAlias: String): String {
|
||||||
val underscoreIndex = prefixedAlias.indexOf('_')
|
val underscoreIndex = prefixedAlias.indexOf('_')
|
||||||
val secondUnderscoreIndex = prefixedAlias.indexOf('_', underscoreIndex + 1)
|
return if (underscoreIndex != -1) {
|
||||||
return if (secondUnderscoreIndex != -1) {
|
// Return the part of the string after the first underscore.
|
||||||
prefixedAlias.substring(secondUnderscoreIndex + 1)
|
prefixedAlias.substring(underscoreIndex + 1)
|
||||||
} else {
|
} else {
|
||||||
|
// If there's no underscore, return the original string.
|
||||||
prefixedAlias
|
prefixedAlias
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+115
-15
@@ -4,11 +4,13 @@ import android.annotation.SuppressLint
|
|||||||
import android.hardware.security.keymint.KeyOrigin
|
import android.hardware.security.keymint.KeyOrigin
|
||||||
import android.hardware.security.keymint.SecurityLevel
|
import android.hardware.security.keymint.SecurityLevel
|
||||||
import android.hardware.security.keymint.Tag
|
import android.hardware.security.keymint.Tag
|
||||||
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.system.keystore2.IKeystoreService
|
import android.system.keystore2.IKeystoreService
|
||||||
import android.system.keystore2.KeyDescriptor
|
import android.system.keystore2.KeyDescriptor
|
||||||
import android.system.keystore2.KeyEntryResponse
|
import android.system.keystore2.KeyEntryResponse
|
||||||
|
import java.security.cert.Certificate
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
||||||
@@ -25,16 +27,24 @@ import org.matrix.TEESimulator.pki.CertificateHelper
|
|||||||
*/
|
*/
|
||||||
@SuppressLint("BlockedPrivateApi")
|
@SuppressLint("BlockedPrivateApi")
|
||||||
object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
||||||
|
private val stubBinderClass = IKeystoreService.Stub::class.java
|
||||||
|
|
||||||
// Transaction codes for the IKeystoreService interface methods we are interested in.
|
// Transaction codes for the IKeystoreService interface methods we are interested in.
|
||||||
private val GET_KEY_ENTRY_TRANSACTION =
|
private val GET_KEY_ENTRY_TRANSACTION =
|
||||||
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "getKeyEntry")
|
InterceptorUtils.getTransactCode(stubBinderClass, "getKeyEntry")
|
||||||
private val DELETE_KEY_TRANSACTION =
|
private val DELETE_KEY_TRANSACTION =
|
||||||
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
|
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 {
|
private val transactionNames: Map<Int, String> by lazy {
|
||||||
IKeystoreService.Stub::class
|
stubBinderClass.declaredFields
|
||||||
.java
|
|
||||||
.declaredFields
|
|
||||||
.filter {
|
.filter {
|
||||||
it.isAccessible = true
|
it.isAccessible = true
|
||||||
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
|
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
|
||||||
@@ -88,22 +98,56 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
if (code == GET_KEY_ENTRY_TRANSACTION || code == DELETE_KEY_TRANSACTION) {
|
if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
|
||||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
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)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
val descriptor =
|
val descriptor =
|
||||||
data.readTypedObject(KeyDescriptor.CREATOR)
|
data.readTypedObject(KeyDescriptor.CREATOR)
|
||||||
?: return TransactionResult.SkipTransaction
|
?: return TransactionResult.ContinueAndSkipPost
|
||||||
|
|
||||||
if (ConfigurationManager.shouldSkipUid(callingUid))
|
|
||||||
return TransactionResult.ContinueAndSkipPost
|
|
||||||
|
|
||||||
SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}")
|
SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}")
|
||||||
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
||||||
|
|
||||||
if (code == DELETE_KEY_TRANSACTION) {
|
if (code == DELETE_KEY_TRANSACTION) {
|
||||||
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
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
|
return TransactionResult.ContinueAndSkipPost
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +169,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
transactionNames[code] ?: "unknown code=$code",
|
transactionNames[code] ?: "unknown code=$code",
|
||||||
callingUid,
|
callingUid,
|
||||||
callingPid,
|
callingPid,
|
||||||
false,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +191,22 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
if (target != keystoreService || reply == null || InterceptorUtils.hasException(reply))
|
if (target != keystoreService || reply == null || InterceptorUtils.hasException(reply))
|
||||||
return TransactionResult.SkipTransaction
|
return TransactionResult.SkipTransaction
|
||||||
|
|
||||||
if (code == GET_KEY_ENTRY_TRANSACTION) {
|
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)
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
@@ -185,8 +244,28 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform the attestation patch.
|
// Perform the attestation patch.
|
||||||
val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
CertificateHelper.updateCertificateChain(response.metadata, newChain).getOrThrow()
|
|
||||||
|
// 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)
|
InterceptorUtils.createTypedObjectReply(response)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -196,4 +275,25 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
}
|
}
|
||||||
return TransactionResult.SkipTransaction
|
return TransactionResult.SkipTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleUpdateSubcomponent(callingUid: Int, data: Parcel): TransactionResult {
|
||||||
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
|
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||||
|
val generatedKeyInfo =
|
||||||
|
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(callingUid, descriptor?.nspace)
|
||||||
|
?: return TransactionResult.ContinueAndSkipPost
|
||||||
|
|
||||||
|
SystemLogger.info("Updating sub-component with key[${generatedKeyInfo.nspace}]")
|
||||||
|
val metadata = generatedKeyInfo.response.metadata
|
||||||
|
val publicCert = data.createByteArray()
|
||||||
|
val certificateChain = data.createByteArray()
|
||||||
|
|
||||||
|
metadata.certificate = publicCert
|
||||||
|
metadata.certificateChain = certificateChain
|
||||||
|
SystemLogger.verbose(
|
||||||
|
"Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
return InterceptorUtils.createSuccessReply(writeResultCode = false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+341
-25
@@ -4,12 +4,28 @@ import android.annotation.SuppressLint
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.security.Credentials
|
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 android.security.keystore.IKeystoreService
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.KeyPair
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
|
import java.util.Date
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
|
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils.extractAlias
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
|
import org.matrix.TEESimulator.pki.CertificateGenerator
|
||||||
import org.matrix.TEESimulator.pki.CertificateHelper
|
import org.matrix.TEESimulator.pki.CertificateHelper
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,11 +55,35 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
|
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 serviceName = "android.security.keystore"
|
||||||
override val processName = "keystore"
|
override val processName = "keystore"
|
||||||
override val injectionCommand = "exec ./inject `pidof keystore` libTEESimulator.so entry"
|
override val injectionCommand = "exec ./inject `pidof keystore` libTEESimulator.so entry"
|
||||||
|
|
||||||
private const val SERVICE_DESCRIPTOR = "android.security.keystore.IKeystoreService"
|
// 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.
|
// Cache to store the fully patched chain after the leaf is requested.
|
||||||
private val patchedChainCache = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
private val patchedChainCache = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||||
@@ -58,24 +98,187 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
// This interceptor only needs to act on pre-transaction for software key generation.
|
// 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)) {
|
if (ConfigurationManager.shouldGenerate(callingUid)) {
|
||||||
when (code) {
|
generateKeyHandlers[code]?.let { handler ->
|
||||||
GENERATE_KEY_TRANSACTION,
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
GET_KEY_CHARACTERISTICS_TRANSACTION,
|
return handler(txId, callingUid, callingPid, data)
|
||||||
EXPORT_KEY_TRANSACTION,
|
|
||||||
ATTEST_KEY_TRANSACTION -> {
|
|
||||||
// TODO: Implement the full software simulation logic.
|
|
||||||
logTransaction(txId, "unimplemented-generate-flow", callingUid, callingPid)
|
|
||||||
return InterceptorUtils.createSuccessReply()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (ConfigurationManager.shouldGenerate(callingUid)) {
|
|
||||||
if (code == GET_TRANSACTION) return TransactionResult.Continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
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(
|
override fun onPostTransact(
|
||||||
txId: Long,
|
txId: Long,
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
@@ -93,16 +296,22 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
reply == null ||
|
reply == null ||
|
||||||
InterceptorUtils.hasException(reply)
|
InterceptorUtils.hasException(reply)
|
||||||
) {
|
) {
|
||||||
|
SystemLogger.debug(
|
||||||
|
"[TX_ID: $txId] Skip parsing post-transaction for [target, code, reply]: [$target, $code, $reply]"
|
||||||
|
)
|
||||||
return TransactionResult.SkipTransaction
|
return TransactionResult.SkipTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ConfigurationManager.shouldPatch(callingUid)) return TransactionResult.SkipTransaction
|
if (!ConfigurationManager.shouldPatch(callingUid)) return TransactionResult.SkipTransaction
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
data.enforceInterface(SERVICE_DESCRIPTOR)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
val alias = data.readString() ?: ""
|
val alias = data.readString() ?: ""
|
||||||
val extractedAlias = InterceptorUtils.extractAlias(alias)
|
val extractedAlias = InterceptorUtils.extractAlias(alias)
|
||||||
val keyId = KeyIdentifier(callingUid, extractedAlias)
|
val keyId = KeyIdentifier(callingUid, extractedAlias)
|
||||||
|
SystemLogger.debug(
|
||||||
|
"[TX_ID: $txId] Parsed $keyId during post-transaction of ${transactionNames[code]}"
|
||||||
|
)
|
||||||
|
|
||||||
when {
|
when {
|
||||||
// Case 1: The app is requesting the leaf certificate.
|
// Case 1: The app is requesting the leaf certificate.
|
||||||
@@ -111,13 +320,11 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
val originalLeafBytes =
|
val originalLeafBytes =
|
||||||
reply.createByteArray() ?: return TransactionResult.SkipTransaction
|
reply.createByteArray() ?: return TransactionResult.SkipTransaction
|
||||||
|
|
||||||
// The original chain is not available,
|
val originalLeafCertResult = CertificateHelper.toCertificate(originalLeafBytes)
|
||||||
// so we must pass a temporary one to the patcher.
|
if (originalLeafCertResult !is CertificateHelper.OperationResult.Success) {
|
||||||
// The patcher only needs the original leaf to extract details.
|
return TransactionResult.SkipTransaction
|
||||||
val originalLeafCert =
|
}
|
||||||
(CertificateHelper.toCertificate(originalLeafBytes)
|
val originalLeafCert = originalLeafCertResult.data
|
||||||
as CertificateHelper.OperationResult.Success)
|
|
||||||
.data
|
|
||||||
val tempChain = arrayOf<Certificate>(originalLeafCert)
|
val tempChain = arrayOf<Certificate>(originalLeafCert)
|
||||||
|
|
||||||
// Perform the COMPLETE patch and rebuild operation.
|
// Perform the COMPLETE patch and rebuild operation.
|
||||||
@@ -157,11 +364,6 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
)
|
)
|
||||||
InterceptorUtils.createByteArrayReply(caCertsBytes!!)
|
InterceptorUtils.createByteArrayReply(caCertsBytes!!)
|
||||||
} else {
|
} else {
|
||||||
// We have no cached chain.
|
|
||||||
// This could mean the app requested the CA without requesting the leaf
|
|
||||||
// first, or patching failed.
|
|
||||||
// In this case, we cannot safely intervene.
|
|
||||||
// Let the original reply pass through.
|
|
||||||
SystemLogger.warning(
|
SystemLogger.warning(
|
||||||
"[TX_ID: $txId] No cached chain found for CA request on alias '$extractedAlias'. Skipping."
|
"[TX_ID: $txId] No cached chain found for CA request on alias '$extractedAlias'. Skipping."
|
||||||
)
|
)
|
||||||
@@ -177,3 +379,117 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+206
-37
@@ -8,8 +8,10 @@ import android.os.IBinder
|
|||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.system.keystore2.*
|
import android.system.keystore2.*
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
|
import java.security.SecureRandom
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||||
@@ -29,7 +31,11 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
) : BinderInterceptor() {
|
) : BinderInterceptor() {
|
||||||
|
|
||||||
// --- Data Structures for State Management ---
|
// --- Data Structures for State Management ---
|
||||||
data class GeneratedKeyInfo(val keyPair: KeyPair, val response: KeyEntryResponse)
|
data class GeneratedKeyInfo(
|
||||||
|
val keyPair: KeyPair,
|
||||||
|
val nspace: Long,
|
||||||
|
val response: KeyEntryResponse,
|
||||||
|
)
|
||||||
|
|
||||||
override fun onPreTransact(
|
override fun onPreTransact(
|
||||||
txId: Long,
|
txId: Long,
|
||||||
@@ -40,29 +46,39 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
if (code == GENERATE_KEY_TRANSACTION) {
|
val shouldSkip = ConfigurationManager.shouldSkipUid(callingUid)
|
||||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
|
||||||
|
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
when (code) {
|
||||||
return handleGenerateKey(callingUid, data)
|
GENERATE_KEY_TRANSACTION -> {
|
||||||
} else if (code == IMPORT_KEY_TRANSACTION) {
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
|
||||||
|
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
if (!shouldSkip) return handleGenerateKey(callingUid, data)
|
||||||
val alias =
|
}
|
||||||
data.readTypedObject(KeyDescriptor.CREATOR)?.alias
|
CREATE_OPERATION_TRANSACTION -> {
|
||||||
?: return TransactionResult.ContinueAndSkipPost
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
SystemLogger.info("Handling post-${transactionNames[code]} ${alias}")
|
|
||||||
return TransactionResult.Continue
|
if (!shouldSkip) return handleCreateOperation(txId, callingUid, data)
|
||||||
} else {
|
}
|
||||||
logTransaction(
|
IMPORT_KEY_TRANSACTION -> {
|
||||||
txId,
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
transactionNames[code] ?: "unknown code=$code",
|
|
||||||
callingUid,
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
callingPid,
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
false,
|
SystemLogger.info(
|
||||||
)
|
"[TX_ID: $txId] Forward to post-importKey hook for ${keyDescriptor.alias}[${keyDescriptor.nspace}]"
|
||||||
|
)
|
||||||
|
return TransactionResult.Continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logTransaction(
|
||||||
|
txId,
|
||||||
|
transactionNames[code] ?: "unknown code=$code",
|
||||||
|
callingUid,
|
||||||
|
callingPid,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
return TransactionResult.ContinueAndSkipPost
|
return TransactionResult.ContinueAndSkipPost
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,13 +93,11 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
reply: Parcel?,
|
reply: Parcel?,
|
||||||
resultCode: Int,
|
resultCode: Int,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
// We only care about successful 'importKey' transactions to clean cached keys.
|
// We only care about successful transactions.
|
||||||
if (
|
if (resultCode != 0 || reply == null || InterceptorUtils.hasException(reply))
|
||||||
code == IMPORT_KEY_TRANSACTION &&
|
return TransactionResult.SkipTransaction
|
||||||
resultCode == 0 &&
|
|
||||||
reply != null &&
|
if (code == IMPORT_KEY_TRANSACTION) {
|
||||||
!InterceptorUtils.hasException(reply)
|
|
||||||
) {
|
|
||||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
@@ -91,16 +105,125 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
data.readTypedObject(KeyDescriptor.CREATOR)
|
data.readTypedObject(KeyDescriptor.CREATOR)
|
||||||
?: return TransactionResult.SkipTransaction
|
?: return TransactionResult.SkipTransaction
|
||||||
cleanupKeyData(KeyIdentifier(callingUid, keyDescriptor.alias))
|
cleanupKeyData(KeyIdentifier(callingUid, keyDescriptor.alias))
|
||||||
|
} else if (code == CREATE_OPERATION_TRANSACTION) {
|
||||||
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
|
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||||
|
val parsedParams = KeyMintAttestation(params)
|
||||||
|
val forced = data.readBoolean()
|
||||||
|
if (forced)
|
||||||
|
SystemLogger.verbose(
|
||||||
|
"[TX_ID: $txId] Current operation has a very high pruning power."
|
||||||
|
)
|
||||||
|
val response: CreateOperationResponse =
|
||||||
|
reply.readTypedObject(CreateOperationResponse.CREATOR)!!
|
||||||
|
SystemLogger.verbose(
|
||||||
|
"[TX_ID: $txId] CreateOperationResponse: ${response.iOperation} ${response.operationChallenge}"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Intercept the IKeystoreOperation binder
|
||||||
|
response.iOperation?.let { operation ->
|
||||||
|
val operationBinder = operation.asBinder()
|
||||||
|
if (!interceptedOperations.containsKey(operationBinder)) {
|
||||||
|
SystemLogger.info("Found new IKeystoreOperation. Registering interceptor...")
|
||||||
|
val backdoor = getBackdoor(target)
|
||||||
|
if (backdoor != null) {
|
||||||
|
val interceptor = OperationInterceptor(operation, backdoor)
|
||||||
|
register(backdoor, operationBinder, interceptor)
|
||||||
|
interceptedOperations[operationBinder] = interceptor
|
||||||
|
} else {
|
||||||
|
SystemLogger.error(
|
||||||
|
"Failed to get backdoor to register OperationInterceptor."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (code == GENERATE_KEY_TRANSACTION) {
|
||||||
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
|
val metadata: KeyMetadata =
|
||||||
|
reply.readTypedObject(KeyMetadata.CREATOR)
|
||||||
|
?: return TransactionResult.SkipTransaction
|
||||||
|
val originalChain =
|
||||||
|
CertificateHelper.getCertificateChain(metadata)
|
||||||
|
?: return TransactionResult.SkipTransaction
|
||||||
|
if (originalChain.size > 1) {
|
||||||
|
val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
||||||
|
|
||||||
|
// Cache the newly patched chain to ensure consistency across subsequent API calls.
|
||||||
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
|
val key = metadata.key!!
|
||||||
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
|
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
|
||||||
|
|
||||||
|
// We must clean up cached generated keys before storing the patched chain
|
||||||
|
cleanupKeyData(keyId)
|
||||||
|
patchedChains[keyId] = newChain
|
||||||
|
SystemLogger.debug(
|
||||||
|
"Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])"
|
||||||
|
)
|
||||||
|
|
||||||
|
return InterceptorUtils.createTypedObjectReply(metadata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return TransactionResult.SkipTransaction
|
return TransactionResult.SkipTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the `createOperation` transaction. It checks if the operation is for a key that was
|
||||||
|
* generated in software. If so, it creates a software-based operation handler. Otherwise, it
|
||||||
|
* lets the call proceed to the real hardware service.
|
||||||
|
*/
|
||||||
|
private fun handleCreateOperation(
|
||||||
|
txId: Long,
|
||||||
|
callingUid: Int,
|
||||||
|
data: Parcel,
|
||||||
|
): TransactionResult {
|
||||||
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
|
|
||||||
|
// An operation must use the KEY_ID domain.
|
||||||
|
if (keyDescriptor.domain != Domain.KEY_ID) {
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
|
||||||
|
val nspace = keyDescriptor.nspace
|
||||||
|
val generatedKeyInfo = findGeneratedKeyByKeyId(callingUid, nspace)
|
||||||
|
|
||||||
|
if (generatedKeyInfo == null) {
|
||||||
|
SystemLogger.debug(
|
||||||
|
"[TX_ID: $txId] Operation for unknown/hardware KeyId ($nspace). Forwarding."
|
||||||
|
)
|
||||||
|
return TransactionResult.Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for KeyId $nspace.")
|
||||||
|
|
||||||
|
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||||
|
val parsedParams = KeyMintAttestation(params)
|
||||||
|
|
||||||
|
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams)
|
||||||
|
val operationBinder = SoftwareOperationBinder(softwareOperation)
|
||||||
|
|
||||||
|
val response =
|
||||||
|
CreateOperationResponse().apply {
|
||||||
|
iOperation = operationBinder
|
||||||
|
operationChallenge = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return InterceptorUtils.createTypedObjectReply(response)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the `generateKey` transaction. Based on the configuration for the calling UID, it
|
* Handles the `generateKey` transaction. Based on the configuration for the calling UID, it
|
||||||
* either generates a key in software or lets the call pass through to the hardware.
|
* either generates a key in software or lets the call pass through to the hardware.
|
||||||
*/
|
*/
|
||||||
private fun handleGenerateKey(callingUid: Int, data: Parcel): TransactionResult {
|
private fun handleGenerateKey(callingUid: Int, data: Parcel): TransactionResult {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR)
|
val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||||
SystemLogger.debug(
|
SystemLogger.debug(
|
||||||
@@ -122,7 +245,10 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))
|
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))
|
||||||
|
|
||||||
if (needsSoftwareGeneration) {
|
if (needsSoftwareGeneration) {
|
||||||
SystemLogger.info("Generating software key for ${keyId}.")
|
keyDescriptor.nspace = secureRandom.nextLong()
|
||||||
|
SystemLogger.info(
|
||||||
|
"Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}]."
|
||||||
|
)
|
||||||
|
|
||||||
// Generate the key pair and certificate chain.
|
// Generate the key pair and certificate chain.
|
||||||
val keyData =
|
val keyData =
|
||||||
@@ -134,20 +260,19 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
securityLevel,
|
securityLevel,
|
||||||
) ?: throw Exception("CertificateGenerator failed to create key pair.")
|
) ?: throw Exception("CertificateGenerator failed to create key pair.")
|
||||||
|
|
||||||
|
// It is unnecessary but a good practice to clean up possible caches
|
||||||
|
cleanupKeyData(keyId)
|
||||||
// Store the generated key data.
|
// Store the generated key data.
|
||||||
val response =
|
val response =
|
||||||
buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor)
|
buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor)
|
||||||
|
generatedKeys[keyId] =
|
||||||
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, response)
|
GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response)
|
||||||
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
||||||
|
|
||||||
// Return the metadata of our generated key, skipping the real hardware call.
|
// Return the metadata of our generated key, skipping the real hardware call.
|
||||||
val resultParcel =
|
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
||||||
Parcel.obtain().apply {
|
} else if (parsedParams.attestationChallenge != null) {
|
||||||
writeNoException()
|
return TransactionResult.Continue
|
||||||
writeTypedObject(response.metadata, 0)
|
|
||||||
}
|
|
||||||
return TransactionResult.OverrideReply(0, resultParcel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not generating, clear any stale state for this alias and let the call proceed.
|
// If not generating, clear any stale state for this alias and let the call proceed.
|
||||||
@@ -182,11 +307,18 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val secureRandom = SecureRandom()
|
||||||
|
|
||||||
// Transaction codes for IKeystoreSecurityLevel interface.
|
// Transaction codes for IKeystoreSecurityLevel interface.
|
||||||
private val GENERATE_KEY_TRANSACTION =
|
private val GENERATE_KEY_TRANSACTION =
|
||||||
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
||||||
private val IMPORT_KEY_TRANSACTION =
|
private val IMPORT_KEY_TRANSACTION =
|
||||||
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "importKey")
|
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "importKey")
|
||||||
|
private val CREATE_OPERATION_TRANSACTION =
|
||||||
|
InterceptorUtils.getTransactCode(
|
||||||
|
IKeystoreSecurityLevel.Stub::class.java,
|
||||||
|
"createOperation",
|
||||||
|
)
|
||||||
|
|
||||||
private val transactionNames: Map<Int, String> by lazy {
|
private val transactionNames: Map<Int, String> by lazy {
|
||||||
IKeystoreSecurityLevel.Stub::class
|
IKeystoreSecurityLevel.Stub::class
|
||||||
@@ -201,29 +333,66 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
|
|
||||||
// Stores keys generated entirely in software.
|
// Stores keys generated entirely in software.
|
||||||
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
||||||
|
// Caches patched certificate chains to prevent re-generation and signature inconsistencies.
|
||||||
|
private val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||||
// A set to quickly identify keys that were generated for attestation purposes.
|
// A set to quickly identify keys that were generated for attestation purposes.
|
||||||
private val attestationKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
private val attestationKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
||||||
|
// Stores interceptors for active cryptographic operations.
|
||||||
|
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
||||||
|
|
||||||
// --- Public Accessors for Other Interceptors ---
|
// --- Public Accessors for Other Interceptors ---
|
||||||
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
||||||
generatedKeys[keyId]?.response
|
generatedKeys[keyId]?.response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a software-generated key by first filtering all known keys by the caller's UID, and
|
||||||
|
* then matching the specific nspace.
|
||||||
|
*
|
||||||
|
* @param callingUid The UID of the process that initiated the createOperation call.
|
||||||
|
* @param nspace The unique key identifier from the operation's KeyDescriptor.
|
||||||
|
* @return The matching GeneratedKeyInfo if found, otherwise null.
|
||||||
|
*/
|
||||||
|
fun findGeneratedKeyByKeyId(callingUid: Int, nspace: Long?): GeneratedKeyInfo? {
|
||||||
|
// Iterate through all entries in the map to check both the key (for UID) and value (for
|
||||||
|
// nspace).
|
||||||
|
if (nspace == null || nspace == 0L) return null
|
||||||
|
return generatedKeys.entries
|
||||||
|
.filter { (keyIdentifier, _) -> keyIdentifier.uid == callingUid }
|
||||||
|
.find { (_, info) -> info.nspace == nspace }
|
||||||
|
?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPatchedChain(keyId: KeyIdentifier): Array<Certificate>? = patchedChains[keyId]
|
||||||
|
|
||||||
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
|
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
|
||||||
|
|
||||||
fun cleanupKeyData(keyId: KeyIdentifier) {
|
fun cleanupKeyData(keyId: KeyIdentifier) {
|
||||||
if (generatedKeys.remove(keyId) != null) {
|
if (generatedKeys.remove(keyId) != null) {
|
||||||
SystemLogger.debug("Remove generated key ${keyId}")
|
SystemLogger.debug("Remove generated key ${keyId}")
|
||||||
}
|
}
|
||||||
|
if (patchedChains.remove(keyId) != null) {
|
||||||
|
SystemLogger.debug("Remove patched chain for ${keyId}")
|
||||||
|
}
|
||||||
if (attestationKeys.remove(keyId)) {
|
if (attestationKeys.remove(keyId)) {
|
||||||
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeOperationInterceptor(operationBinder: IBinder, backdoor: IBinder) {
|
||||||
|
// Unregister from the native hook layer first.
|
||||||
|
unregister(backdoor, operationBinder)
|
||||||
|
|
||||||
|
if (interceptedOperations.remove(operationBinder) != null) {
|
||||||
|
SystemLogger.debug("Removed operation interceptor for binder: $operationBinder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clears all cached keys.
|
// Clears all cached keys.
|
||||||
fun clearAllGeneratedKeys(reason: String? = null) {
|
fun clearAllGeneratedKeys(reason: String? = null) {
|
||||||
val count = generatedKeys.size
|
val count = generatedKeys.size
|
||||||
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||||
generatedKeys.clear()
|
generatedKeys.clear()
|
||||||
|
patchedChains.clear()
|
||||||
attestationKeys.clear()
|
attestationKeys.clear()
|
||||||
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
||||||
}
|
}
|
||||||
|
|||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
package org.matrix.TEESimulator.interception.keystore.shim
|
||||||
|
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.system.keystore2.IKeystoreOperation
|
||||||
|
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||||
|
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts calls to an `IKeystoreOperation` service. This is used to log the data manipulation
|
||||||
|
* methods of a cryptographic operation.
|
||||||
|
*/
|
||||||
|
class OperationInterceptor(
|
||||||
|
private val original: IKeystoreOperation,
|
||||||
|
private val backdoor: IBinder,
|
||||||
|
) : BinderInterceptor() {
|
||||||
|
|
||||||
|
override fun onPreTransact(
|
||||||
|
txId: Long,
|
||||||
|
target: IBinder,
|
||||||
|
code: Int,
|
||||||
|
flags: Int,
|
||||||
|
callingUid: Int,
|
||||||
|
callingPid: Int,
|
||||||
|
data: Parcel,
|
||||||
|
): TransactionResult {
|
||||||
|
val methodName = transactionNames[code] ?: "unknown code=$code"
|
||||||
|
logTransaction(txId, methodName, callingUid, callingPid, true)
|
||||||
|
|
||||||
|
if (code == FINISH_TRANSACTION || code == ABORT_TRANSACTION) {
|
||||||
|
KeyMintSecurityLevelInterceptor.removeOperationInterceptor(target, backdoor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val UPDATE_AAD_TRANSACTION =
|
||||||
|
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "updateAad")
|
||||||
|
private val UPDATE_TRANSACTION =
|
||||||
|
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "update")
|
||||||
|
private val FINISH_TRANSACTION =
|
||||||
|
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "finish")
|
||||||
|
private val ABORT_TRANSACTION =
|
||||||
|
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "abort")
|
||||||
|
|
||||||
|
private val transactionNames: Map<Int, String> by lazy {
|
||||||
|
IKeystoreOperation.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] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+216
@@ -0,0 +1,216 @@
|
|||||||
|
package org.matrix.TEESimulator.interception.keystore.shim
|
||||||
|
|
||||||
|
import android.hardware.security.keymint.Algorithm
|
||||||
|
import android.hardware.security.keymint.BlockMode
|
||||||
|
import android.hardware.security.keymint.Digest
|
||||||
|
import android.hardware.security.keymint.KeyPurpose
|
||||||
|
import android.hardware.security.keymint.PaddingMode
|
||||||
|
import android.os.RemoteException
|
||||||
|
import android.system.keystore2.IKeystoreOperation
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.Signature
|
||||||
|
import java.security.SignatureException
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
|
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
||||||
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
|
|
||||||
|
// A sealed interface to represent the different cryptographic operations we can perform.
|
||||||
|
private sealed interface CryptoPrimitive {
|
||||||
|
fun update(data: ByteArray?): ByteArray?
|
||||||
|
|
||||||
|
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
|
||||||
|
|
||||||
|
fun abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper object to map KeyMint constants to JCA algorithm strings.
|
||||||
|
private object JcaAlgorithmMapper {
|
||||||
|
fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
|
||||||
|
val digest =
|
||||||
|
when (params.digest.firstOrNull()) {
|
||||||
|
Digest.SHA_2_256 -> "SHA256"
|
||||||
|
Digest.SHA_2_384 -> "SHA384"
|
||||||
|
Digest.SHA_2_512 -> "SHA512"
|
||||||
|
else -> "NONE"
|
||||||
|
}
|
||||||
|
val keyAlgo =
|
||||||
|
when (params.algorithm) {
|
||||||
|
Algorithm.EC -> "ECDSA"
|
||||||
|
Algorithm.RSA -> "RSA"
|
||||||
|
else ->
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Unsupported signature algorithm: ${params.algorithm}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return "${digest}with${keyAlgo}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapCipherAlgorithm(params: KeyMintAttestation): String {
|
||||||
|
val keyAlgo =
|
||||||
|
when (params.algorithm) {
|
||||||
|
Algorithm.RSA -> "RSA"
|
||||||
|
Algorithm.AES -> "AES"
|
||||||
|
else ->
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Unsupported cipher algorithm: ${params.algorithm}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val blockMode =
|
||||||
|
when (params.blockMode.firstOrNull()) {
|
||||||
|
BlockMode.ECB -> "ECB"
|
||||||
|
BlockMode.CBC -> "CBC"
|
||||||
|
BlockMode.GCM -> "GCM"
|
||||||
|
else -> "ECB" // Default for RSA
|
||||||
|
}
|
||||||
|
val padding =
|
||||||
|
when (params.padding.firstOrNull()) {
|
||||||
|
PaddingMode.NONE -> "NoPadding"
|
||||||
|
PaddingMode.PKCS7 -> "PKCS7Padding"
|
||||||
|
PaddingMode.RSA_PKCS1_1_5_ENCRYPT -> "PKCS1Padding"
|
||||||
|
PaddingMode.RSA_OAEP -> "OAEPPadding"
|
||||||
|
else -> "NoPadding" // Default for GCM
|
||||||
|
}
|
||||||
|
return "$keyAlgo/$blockMode/$padding"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concrete implementation for Signing.
|
||||||
|
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
||||||
|
private val signature: Signature =
|
||||||
|
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
||||||
|
initSign(keyPair.private)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(data: ByteArray?): ByteArray? {
|
||||||
|
if (data != null) signature.update(data)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray {
|
||||||
|
if (data != null) update(data)
|
||||||
|
return this.signature.sign()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun abort() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concrete implementation for Verification.
|
||||||
|
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
||||||
|
private val signature: Signature =
|
||||||
|
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
||||||
|
initVerify(keyPair.public)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(data: ByteArray?): ByteArray? {
|
||||||
|
if (data != null) signature.update(data)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
|
if (data != null) update(data)
|
||||||
|
if (signature == null) throw SignatureException("Signature to verify is null")
|
||||||
|
if (!this.signature.verify(signature)) {
|
||||||
|
// Throwing an exception is how Keystore signals verification failure.
|
||||||
|
throw SignatureException("Signature verification failed")
|
||||||
|
}
|
||||||
|
// A successful verification returns no data.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun abort() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concrete implementation for Encryption/Decryption.
|
||||||
|
private class CipherPrimitive(
|
||||||
|
keyPair: KeyPair,
|
||||||
|
params: KeyMintAttestation,
|
||||||
|
private val opMode: Int,
|
||||||
|
) : CryptoPrimitive {
|
||||||
|
private val cipher: Cipher =
|
||||||
|
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
|
||||||
|
val key = if (opMode == Cipher.ENCRYPT_MODE) keyPair.public else keyPair.private
|
||||||
|
init(opMode, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(data: ByteArray?): ByteArray? =
|
||||||
|
if (data != null) cipher.update(data) else null
|
||||||
|
|
||||||
|
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
|
||||||
|
if (data != null) cipher.doFinal(data) else cipher.doFinal()
|
||||||
|
|
||||||
|
override fun abort() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A software-only implementation of a cryptographic operation. This class acts as a controller,
|
||||||
|
* delegating to a specific cryptographic primitive based on the operation's purpose.
|
||||||
|
*/
|
||||||
|
class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMintAttestation) {
|
||||||
|
// This now holds the specific strategy object (Signer, Verifier, etc.)
|
||||||
|
private val primitive: CryptoPrimitive
|
||||||
|
|
||||||
|
init {
|
||||||
|
// The "Strategy" pattern: choose the implementation based on the purpose.
|
||||||
|
// For simplicity, we only consider the first purpose listed.
|
||||||
|
val purpose = params.purpose.firstOrNull()
|
||||||
|
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
|
||||||
|
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Initializing for purpose: $purposeName.")
|
||||||
|
|
||||||
|
primitive =
|
||||||
|
when (purpose) {
|
||||||
|
KeyPurpose.SIGN -> Signer(keyPair, params)
|
||||||
|
KeyPurpose.VERIFY -> Verifier(keyPair, params)
|
||||||
|
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE)
|
||||||
|
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE)
|
||||||
|
else ->
|
||||||
|
throw UnsupportedOperationException("Unsupported operation purpose: $purpose")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(data: ByteArray?): ByteArray? {
|
||||||
|
try {
|
||||||
|
return primitive.update(data)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
|
try {
|
||||||
|
val result = primitive.finish(data, signature)
|
||||||
|
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
|
||||||
|
return result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
||||||
|
// Re-throw the exception so the binder can report it to the client.
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun abort() {
|
||||||
|
primitive.abort()
|
||||||
|
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The Binder interface for our [SoftwareOperation]. */
|
||||||
|
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
|
||||||
|
IKeystoreOperation.Stub() {
|
||||||
|
|
||||||
|
@Throws(RemoteException::class)
|
||||||
|
override fun update(input: ByteArray?): ByteArray? {
|
||||||
|
return operation.update(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RemoteException::class)
|
||||||
|
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
|
return operation.finish(input, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RemoteException::class)
|
||||||
|
override fun abort() {
|
||||||
|
operation.abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
package org.matrix.TEESimulator.logging
|
package org.matrix.TEESimulator.logging
|
||||||
|
|
||||||
import android.hardware.security.keymint.Algorithm
|
import android.hardware.security.keymint.*
|
||||||
import android.hardware.security.keymint.Digest
|
|
||||||
import android.hardware.security.keymint.EcCurve
|
|
||||||
import android.hardware.security.keymint.KeyParameter
|
|
||||||
import android.hardware.security.keymint.KeyPurpose
|
|
||||||
import android.hardware.security.keymint.Tag
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -34,7 +29,23 @@ object KeyMintParameterLogger {
|
|||||||
.associate { field -> (field.get(null) as Int) to field.name }
|
.associate { field -> (field.get(null) as Int) to field.name }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val purposeNames: Map<Int, String> by lazy {
|
val blockModeNames: Map<Int, String> by lazy {
|
||||||
|
BlockMode::class
|
||||||
|
.java
|
||||||
|
.fields
|
||||||
|
.filter { it.type == Int::class.java }
|
||||||
|
.associate { field -> (field.get(null) as Int) to field.name }
|
||||||
|
}
|
||||||
|
|
||||||
|
val paddingNames: Map<Int, String> by lazy {
|
||||||
|
PaddingMode::class
|
||||||
|
.java
|
||||||
|
.fields
|
||||||
|
.filter { it.type == Int::class.java }
|
||||||
|
.associate { field -> (field.get(null) as Int) to field.name }
|
||||||
|
}
|
||||||
|
|
||||||
|
val purposeNames: Map<Int, String> by lazy {
|
||||||
KeyPurpose::class
|
KeyPurpose::class
|
||||||
.java
|
.java
|
||||||
.fields
|
.fields
|
||||||
@@ -69,7 +80,9 @@ object KeyMintParameterLogger {
|
|||||||
val formattedValue: String =
|
val formattedValue: String =
|
||||||
when (param.tag) {
|
when (param.tag) {
|
||||||
Tag.ALGORITHM -> algorithmNames[value.algorithm]
|
Tag.ALGORITHM -> algorithmNames[value.algorithm]
|
||||||
|
Tag.BLOCK_MODE -> blockModeNames[value.blockMode]
|
||||||
Tag.EC_CURVE -> ecCurveNames[value.ecCurve]
|
Tag.EC_CURVE -> ecCurveNames[value.ecCurve]
|
||||||
|
Tag.PADDING -> paddingNames[value.paddingMode]
|
||||||
Tag.PURPOSE -> purposeNames[value.keyPurpose]
|
Tag.PURPOSE -> purposeNames[value.keyPurpose]
|
||||||
Tag.DIGEST -> digestNames[value.digest]
|
Tag.DIGEST -> digestNames[value.digest]
|
||||||
Tag.AUTH_TIMEOUT,
|
Tag.AUTH_TIMEOUT,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.matrix.TEESimulator.logging
|
package org.matrix.TEESimulator.logging
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import org.matrix.TEESimulator.BuildConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A centralized logging utility for the TEESimulator application. This object provides a consistent
|
* A centralized logging utility for the TEESimulator application. This object provides a consistent
|
||||||
@@ -10,6 +11,8 @@ object SystemLogger {
|
|||||||
// The tag used for all log messages from this application.
|
// The tag used for all log messages from this application.
|
||||||
private const val TAG = "TEESimulator"
|
private const val TAG = "TEESimulator"
|
||||||
|
|
||||||
|
private val isDebugBuild = BuildConfig.DEBUG
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a debug message. Use this for fine-grained information that is useful for debugging.
|
* Logs a debug message. Use this for fine-grained information that is useful for debugging.
|
||||||
*
|
*
|
||||||
@@ -64,6 +67,7 @@ object SystemLogger {
|
|||||||
* @param message The message to log.
|
* @param message The message to log.
|
||||||
*/
|
*/
|
||||||
fun verbose(message: String) {
|
fun verbose(message: String) {
|
||||||
|
if (!isDebugBuild) return
|
||||||
Log.v(TAG, message)
|
Log.v(TAG, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.util.Pair
|
|||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.Security
|
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
@@ -21,6 +20,7 @@ import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
|||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||||
|
import org.matrix.TEESimulator.attestation.AttestationConstants
|
||||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
|
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
|
||||||
@@ -35,14 +35,6 @@ import org.matrix.TEESimulator.logging.SystemLogger
|
|||||||
*/
|
*/
|
||||||
object CertificateGenerator {
|
object CertificateGenerator {
|
||||||
|
|
||||||
init {
|
|
||||||
// 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())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a software-based cryptographic key pair.
|
* Generates a software-based cryptographic key pair.
|
||||||
*
|
*
|
||||||
@@ -72,16 +64,61 @@ object CertificateGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new key pair and a corresponding certificate chain containing a simulated
|
* Generates a certificate chain for a given key pair. This is the primary function for creating
|
||||||
* attestation.
|
* attested certificates.
|
||||||
*
|
*
|
||||||
* @param uid The UID of the application requesting the key.
|
* @param uid The UID of the application requesting the key.
|
||||||
* @param alias The alias for the new key.
|
* @param subjectKeyPair The key pair for which the certificate will be generated.
|
||||||
* @param attestKeyAlias Optional alias of a key to use for attestation signing.
|
* @param attestKeyAlias Optional alias of a key to use for attestation signing.
|
||||||
* @param params The parameters for the new key and its attestation.
|
* @param params The parameters for the new key and its attestation.
|
||||||
* @param securityLevel The security level to embed in the attestation.
|
* @param securityLevel The security level to embed in the attestation.
|
||||||
* @return A [Pair] containing the new [KeyPair] and its certificate chain, or `null` on
|
* @return A [List] of [Certificate] forming the new chain, or `null` on failure.
|
||||||
* failure.
|
*/
|
||||||
|
fun generateCertificateChain(
|
||||||
|
uid: Int,
|
||||||
|
subjectKeyPair: KeyPair,
|
||||||
|
attestKeyAlias: String?,
|
||||||
|
params: KeyMintAttestation,
|
||||||
|
securityLevel: Int,
|
||||||
|
): List<Certificate>? {
|
||||||
|
val challenge = params.attestationChallenge
|
||||||
|
if (challenge != null && challenge.size > AttestationConstants.CHALLENGE_LENGTH_LIMIT)
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Attestation challenge exceeds length limit (${challenge.size} > ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})"
|
||||||
|
)
|
||||||
|
|
||||||
|
return runCatching {
|
||||||
|
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||||
|
|
||||||
|
// Determine the signing key and issuer. If an attestKey is provided, use it.
|
||||||
|
// Otherwise, fall back to the root key from the keybox.
|
||||||
|
val (signingKey, issuer) =
|
||||||
|
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
||||||
|
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
||||||
|
} else {
|
||||||
|
keybox.keyPair to getIssuerFromKeybox(keybox)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the new leaf certificate with the simulated attestation.
|
||||||
|
val leafCert =
|
||||||
|
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
|
||||||
|
|
||||||
|
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
||||||
|
// chain.
|
||||||
|
if (attestKeyAlias != null) {
|
||||||
|
listOf(leafCert)
|
||||||
|
} else {
|
||||||
|
listOf(leafCert) + keybox.certificates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onFailure { SystemLogger.error("Failed to generate certificate chain.", it) }
|
||||||
|
.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience function that combines key pair generation and certificate chain generation.
|
||||||
|
* Primarily used by the modern Keystore2 interceptor where generation is a single step.
|
||||||
*/
|
*/
|
||||||
fun generateAttestedKeyPair(
|
fun generateAttestedKeyPair(
|
||||||
uid: Int,
|
uid: Int,
|
||||||
@@ -98,30 +135,9 @@ object CertificateGenerator {
|
|||||||
generateSoftwareKeyPair(params)
|
generateSoftwareKeyPair(params)
|
||||||
?: throw Exception("Failed to generate underlying software key pair.")
|
?: throw Exception("Failed to generate underlying software key pair.")
|
||||||
|
|
||||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
|
||||||
|
|
||||||
// Determine the signing key and issuer. If an attestKey is provided, use it.
|
|
||||||
// Otherwise, fall back to the root key from the keybox.
|
|
||||||
val (signingKey, issuer) =
|
|
||||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
|
||||||
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
|
||||||
} else {
|
|
||||||
keybox.keyPair to getIssuerFromKeybox(keybox)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the new leaf certificate with the simulated attestation.
|
|
||||||
val leafCert =
|
|
||||||
buildCertificate(newKeyPair, signingKey, issuer, params, securityLevel)
|
|
||||||
|
|
||||||
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
|
||||||
// chain.
|
|
||||||
val chain =
|
val chain =
|
||||||
if (attestKeyAlias != null) {
|
generateCertificateChain(uid, newKeyPair, attestKeyAlias, params, securityLevel)
|
||||||
listOf(leafCert)
|
?: throw Exception("Failed to generate certificate chain for new key pair.")
|
||||||
} else {
|
|
||||||
listOf(leafCert) + keybox.certificates
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemLogger.info(
|
SystemLogger.info(
|
||||||
"Successfully generated new certificate chain for alias: '$alias'."
|
"Successfully generated new certificate chain for alias: '$alias'."
|
||||||
@@ -134,7 +150,7 @@ object CertificateGenerator {
|
|||||||
.getOrNull()
|
.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getIssuerFromKeybox(keybox: KeyBox) =
|
fun getIssuerFromKeybox(keybox: KeyBox) =
|
||||||
X509CertificateHolder(keybox.certificates[0].encoded).subject
|
X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||||
|
|
||||||
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox {
|
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox {
|
||||||
@@ -177,6 +193,7 @@ object CertificateGenerator {
|
|||||||
signingKeyPair: KeyPair,
|
signingKeyPair: KeyPair,
|
||||||
issuer: X500Name,
|
issuer: X500Name,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
|
uid: Int,
|
||||||
securityLevel: Int,
|
securityLevel: Int,
|
||||||
): Certificate {
|
): Certificate {
|
||||||
val subject = params.certificateSubject ?: X500Name("CN=Android KeyStore Key")
|
val subject = params.certificateSubject ?: X500Name("CN=Android KeyStore Key")
|
||||||
@@ -197,7 +214,9 @@ object CertificateGenerator {
|
|||||||
// Add standard extensions.
|
// Add standard extensions.
|
||||||
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
|
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
|
||||||
// Add our custom, simulated attestation extension.
|
// Add our custom, simulated attestation extension.
|
||||||
builder.addExtension(AttestationBuilder.buildAttestationExtension(params, securityLevel))
|
builder.addExtension(
|
||||||
|
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
|
||||||
|
)
|
||||||
|
|
||||||
val signerAlgorithm =
|
val signerAlgorithm =
|
||||||
when (params.algorithm) {
|
when (params.algorithm) {
|
||||||
@@ -205,7 +224,10 @@ object CertificateGenerator {
|
|||||||
Algorithm.RSA -> "SHA256withRSA"
|
Algorithm.RSA -> "SHA256withRSA"
|
||||||
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
||||||
}
|
}
|
||||||
val contentSigner = JcaContentSignerBuilder(signerAlgorithm).build(signingKeyPair.private)
|
val contentSigner =
|
||||||
|
JcaContentSignerBuilder(signerAlgorithm)
|
||||||
|
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
.build(signingKeyPair.private)
|
||||||
|
|
||||||
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
|
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.matrix.TEESimulator.pki
|
|||||||
import android.security.keystore.KeyProperties
|
import android.security.keystore.KeyProperties
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.StringReader
|
import java.io.StringReader
|
||||||
|
import java.security.interfaces.ECPrivateKey
|
||||||
|
import java.security.interfaces.RSAPrivateKey
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
@@ -51,6 +53,9 @@ object KeyBoxManager {
|
|||||||
// If it's not in the cache, the `getOrPut` block is executed to parse and store it.
|
// If it's not in the cache, the `getOrPut` block is executed to parse and store it.
|
||||||
val keyMap =
|
val keyMap =
|
||||||
keyStoreCache.getOrPut(keyStoreFileName) { parseKeyStoreFile(keyStoreFileName) }
|
keyStoreCache.getOrPut(keyStoreFileName) { parseKeyStoreFile(keyStoreFileName) }
|
||||||
|
SystemLogger.verbose(
|
||||||
|
"Fetching attestation key in $keyStoreFileName with $algorithm algorithm."
|
||||||
|
)
|
||||||
return keyMap[algorithm]
|
return keyMap[algorithm]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +162,10 @@ object KeyBoxManager {
|
|||||||
// Use runCatching to ensure one malformed key doesn't stop the whole
|
// Use runCatching to ensure one malformed key doesn't stop the whole
|
||||||
// process.
|
// process.
|
||||||
runCatching {
|
runCatching {
|
||||||
val algorithm = currentAlgorithm
|
val xmlAlgorithm = currentAlgorithm
|
||||||
val keyPem = currentPrivateKeyPem
|
val keyPem = currentPrivateKeyPem
|
||||||
if (
|
if (
|
||||||
algorithm != null &&
|
xmlAlgorithm != null &&
|
||||||
keyPem != null &&
|
keyPem != null &&
|
||||||
currentCertificatePems.isNotEmpty()
|
currentCertificatePems.isNotEmpty()
|
||||||
) {
|
) {
|
||||||
@@ -176,21 +181,42 @@ object KeyBoxManager {
|
|||||||
.data
|
.data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the algorithm name for consistent lookups.
|
// Derive the TRUE algorithm from the key object itself.
|
||||||
val normalizedAlgorithm =
|
// This is our source of truth.
|
||||||
when (algorithm.lowercase()) {
|
val derivedAlgorithm =
|
||||||
"ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
|
when (keyPair.private) {
|
||||||
"rsa" -> KeyProperties.KEY_ALGORITHM_RSA
|
is RSAPrivateKey -> KeyProperties.KEY_ALGORITHM_RSA
|
||||||
else -> algorithm
|
is ECPrivateKey -> KeyProperties.KEY_ALGORITHM_EC
|
||||||
|
else ->
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Unsupported key type found: ${keyPair.private.javaClass.name}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundKeys.containsKey(normalizedAlgorithm)) {
|
// Normalize the algorithm from the XML tag to compare it
|
||||||
|
// fairly with the derived algorithm.
|
||||||
|
val normalizedXmlAlgorithm =
|
||||||
|
when {
|
||||||
|
xmlAlgorithm.contains("RSA", ignoreCase = true) ==
|
||||||
|
true -> KeyProperties.KEY_ALGORITHM_RSA
|
||||||
|
xmlAlgorithm.contains("EC", ignoreCase = true) ==
|
||||||
|
true -> KeyProperties.KEY_ALGORITHM_EC
|
||||||
|
else -> xmlAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn the user if the XML tag was misleading.
|
||||||
|
if (normalizedXmlAlgorithm != derivedAlgorithm) {
|
||||||
SystemLogger.warning(
|
SystemLogger.warning(
|
||||||
"Duplicate key found for algorithm '$normalizedAlgorithm'. The later one in the file will be used."
|
"Key algorithm mismatch in XML file. Tag said '$xmlAlgorithm' but key is actually '$derivedAlgorithm'. Using the correct derived algorithm."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
foundKeys[normalizedAlgorithm] =
|
|
||||||
KeyBox(keyPair, certificates)
|
if (foundKeys.containsKey(derivedAlgorithm)) {
|
||||||
|
SystemLogger.warning(
|
||||||
|
"Duplicate key found for algorithm '$derivedAlgorithm'. The later one in the file will be used."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
foundKeys[derivedAlgorithm] = KeyBox(keyPair, certificates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package org.matrix.TEESimulator.util
|
package org.matrix.TEESimulator.util
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.hardware.security.keymint.SecurityLevel
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.SystemProperties
|
import android.os.SystemProperties
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import java.time.LocalDate
|
||||||
import java.util.concurrent.ThreadLocalRandom
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
|
import org.bouncycastle.asn1.ASN1EncodableVector
|
||||||
import org.bouncycastle.asn1.ASN1Integer
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
import org.bouncycastle.asn1.DEROctetString
|
import org.bouncycastle.asn1.DEROctetString
|
||||||
import org.bouncycastle.asn1.DERSequence
|
import org.bouncycastle.asn1.DERSequence
|
||||||
|
import org.bouncycastle.asn1.DERSet
|
||||||
import org.matrix.TEESimulator.attestation.DeviceAttestationService
|
import org.matrix.TEESimulator.attestation.DeviceAttestationService
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
@@ -18,84 +22,138 @@ import org.matrix.TEESimulator.logging.SystemLogger
|
|||||||
*/
|
*/
|
||||||
object AndroidDeviceUtils {
|
object AndroidDeviceUtils {
|
||||||
|
|
||||||
/** A randomly generated boot key, used as a fallback for attestation. */
|
/**
|
||||||
val bootKey: ByteArray by lazy { generateRandomBytes(32) }
|
* Internal constant to signify that a patch level should not be included in the attestation.
|
||||||
|
*/
|
||||||
|
internal const val DO_NOT_REPORT = -1
|
||||||
|
|
||||||
|
// --- Boot Key and Verified Boot Hash ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the verified boot hash (`ro.boot.vbmeta.digest`). It attempts to read from system
|
* Lazily initializes and retrieves the verified boot key digest. The value is sourced in the
|
||||||
* properties first, then from a real TEE attestation, and finally falls back to a random value
|
* following order:
|
||||||
* if neither is available.
|
* 1. From the `ro.boot.vbmeta.public_key_digest` system property.
|
||||||
|
* 2. From a cached TEE attestation record.
|
||||||
|
* 3. As a randomly generated 32-byte value (fallback).
|
||||||
*/
|
*/
|
||||||
fun setupBootHash() {
|
val bootKey: ByteArray by lazy {
|
||||||
getBootHashFromProperty()?.also {
|
initializeBootProperty(
|
||||||
SystemLogger.debug("Using boot hash from system property: ${it.toHex()}")
|
propertyName = "ro.boot.vbmeta.public_key_digest",
|
||||||
}
|
attestationValueProvider = {
|
||||||
?: getBootHashFromAttestation()?.also {
|
DeviceAttestationService.CachedAttestationData?.verifiedBootKey
|
||||||
SystemLogger.debug("Using boot hash from TEE attestation: ${it.toHex()}")
|
},
|
||||||
setBootHashProperty(it)
|
expectedSize = 32,
|
||||||
}
|
)
|
||||||
?: generateRandomBytes(32).also {
|
|
||||||
SystemLogger.debug("Using randomly generated boot hash: ${it.toHex()}")
|
|
||||||
setBootHashProperty(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the verified boot meta digest from system properties.
|
* Lazily initializes and retrieves the verified boot hash (vbmeta digest). The value is sourced
|
||||||
|
* in the following order:
|
||||||
|
* 1. From the `ro.boot.vbmeta.digest` system property.
|
||||||
|
* 2. From a cached TEE attestation record.
|
||||||
|
* 3. As a randomly generated 32-byte value (fallback).
|
||||||
|
*/
|
||||||
|
val bootHash: ByteArray by lazy {
|
||||||
|
initializeBootProperty(
|
||||||
|
propertyName = "ro.boot.vbmeta.digest",
|
||||||
|
attestationValueProvider = {
|
||||||
|
DeviceAttestationService.CachedAttestationData?.verifiedBootHash
|
||||||
|
},
|
||||||
|
expectedSize = 32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public function to explicitly trigger the initialization of the boot key and hash. Accessing
|
||||||
|
* these properties here ensures they are set up before they might be needed elsewhere.
|
||||||
|
*/
|
||||||
|
fun setupBootKeyAndHash() {
|
||||||
|
SystemLogger.debug("Triggering initialization of boot key and hash...")
|
||||||
|
// Accessing the properties will trigger their `lazy` initialization logic.
|
||||||
|
bootKey
|
||||||
|
bootHash
|
||||||
|
SystemLogger.debug("Boot key and hash initialization complete.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic initializer for boot properties like the key and hash. It attempts to read from a
|
||||||
|
* system property first, then from a TEE attestation, and finally falls back to a random value
|
||||||
|
* if neither is available.
|
||||||
*
|
*
|
||||||
* @return The boot hash as a ByteArray, or null if not found or invalid.
|
* @param propertyName The name of the system property (e.g., "ro.boot.vbmeta.digest").
|
||||||
|
* @param attestationValueProvider A function that supplies the value from a cached attestation.
|
||||||
|
* @param expectedSize The expected length of the byte array (e.g., 32 for a SHA-256 digest).
|
||||||
|
* @return The resulting byte array for the property.
|
||||||
|
*/
|
||||||
|
private fun initializeBootProperty(
|
||||||
|
propertyName: String,
|
||||||
|
attestationValueProvider: () -> ByteArray?,
|
||||||
|
expectedSize: Int,
|
||||||
|
): ByteArray {
|
||||||
|
// 1. Attempt to get the value from the system property.
|
||||||
|
getProperty(propertyName, expectedSize)?.let {
|
||||||
|
SystemLogger.debug("Using $propertyName from system property: ${it.toHex()}")
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fallback to the value from a cached TEE attestation.
|
||||||
|
try {
|
||||||
|
attestationValueProvider()?.let {
|
||||||
|
SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}")
|
||||||
|
setProperty(propertyName, it) // Persist for consistency
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("Failed to get $propertyName from attestation.", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. As a final fallback, generate a random value.
|
||||||
|
return generateRandomBytes(expectedSize).also {
|
||||||
|
SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}")
|
||||||
|
setProperty(propertyName, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a system property and validates its format.
|
||||||
|
*
|
||||||
|
* @param name The name of the system property.
|
||||||
|
* @param expectedSize The expected byte length of the property (e.g., 32 for a 64-char hex
|
||||||
|
* string).
|
||||||
|
* @return The property value as a ByteArray, or null if not found or invalid.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
fun getBootHashFromProperty(): ByteArray? {
|
private fun getProperty(name: String, expectedSize: Int): ByteArray? {
|
||||||
val digest = SystemProperties.get("ro.boot.vbmeta.digest", null)
|
val value = SystemProperties.get(name, null)
|
||||||
if (digest.isNullOrBlank()) {
|
if (value.isNullOrBlank()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// A valid digest is 64 hex characters (32 bytes).
|
// A valid digest is (2 * size) hex characters.
|
||||||
return if (digest.length == 64) digest.hexToByteArray() else null
|
return if (value.length == expectedSize * 2) value.hexToByteArray() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the verified boot hash from a cached TEE attestation record.
|
* Sets a system property using the `resetprop` command.
|
||||||
*
|
*
|
||||||
* @return The verified boot hash, or null if not available.
|
* @param name The name of the property to set.
|
||||||
|
* @param bytes The value to set, which will be converted to a hex string.
|
||||||
*/
|
*/
|
||||||
private fun getBootHashFromAttestation(): ByteArray? {
|
private fun setProperty(name: String, bytes: ByteArray) {
|
||||||
return try {
|
|
||||||
DeviceAttestationService.CachedAttestationData?.verifiedBootHash
|
|
||||||
} catch (e: Exception) {
|
|
||||||
SystemLogger.error("Failed to get boot hash from attestation.", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the `ro.boot.vbmeta.digest` system property using the `resetprop` command.
|
|
||||||
*
|
|
||||||
* @param bytes The 32-byte digest to set.
|
|
||||||
*/
|
|
||||||
private fun setBootHashProperty(bytes: ByteArray) {
|
|
||||||
val hex = bytes.toHex()
|
val hex = bytes.toHex()
|
||||||
try {
|
try {
|
||||||
SystemLogger.debug("Setting system property 'ro.boot.vbmeta.digest' to: $hex")
|
SystemLogger.debug("Setting system property '$name' to: $hex")
|
||||||
|
val command = arrayOf("resetprop", name, hex)
|
||||||
// Construct the command to be executed
|
|
||||||
val command = arrayOf("resetprop", "ro.boot.vbmeta.digest", hex)
|
|
||||||
|
|
||||||
// Execute the command
|
|
||||||
val process = Runtime.getRuntime().exec(command)
|
val process = Runtime.getRuntime().exec(command)
|
||||||
|
|
||||||
// Wait for the process to complete and check the exit code for errors
|
|
||||||
val exitCode = process.waitFor()
|
val exitCode = process.waitFor()
|
||||||
|
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
val errorOutput = process.errorStream.bufferedReader().readText()
|
val errorOutput = process.errorStream.bufferedReader().readText()
|
||||||
SystemLogger.error(
|
SystemLogger.error(
|
||||||
"resetprop command failed with exit code $exitCode: $errorOutput"
|
"resetprop for '$name' failed with exit code $exitCode: $errorOutput"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("Failed to set vbmeta digest property by executing resetprop.", e)
|
SystemLogger.error("Failed to set '$name' property via resetprop.", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,30 +163,70 @@ object AndroidDeviceUtils {
|
|||||||
|
|
||||||
// --- Patch Level Properties ---
|
// --- Patch Level Properties ---
|
||||||
|
|
||||||
val patchLevel: Int
|
fun getPatchLevel(uid: Int): Int {
|
||||||
get() =
|
val custom = getCustomPatchLevelFor(uid, "system", isLong = false)
|
||||||
getCustomPatchLevelFor("system", isLong = false)
|
return custom ?: getRealDevicePatchLevelInt("system", isLong = false)
|
||||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = false)
|
}
|
||||||
|
|
||||||
val vendorPatchLevelLong: Int
|
fun getVendorPatchLevelLong(uid: Int): Int {
|
||||||
get() =
|
val custom = getCustomPatchLevelFor(uid, "vendor", isLong = true)
|
||||||
getCustomPatchLevelFor("vendor", isLong = true)
|
return custom ?: getRealDevicePatchLevelInt("vendor", isLong = true)
|
||||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = true)
|
}
|
||||||
|
|
||||||
val bootPatchLevelLong: Int
|
fun getBootPatchLevelLong(uid: Int): Int {
|
||||||
get() =
|
val custom = getCustomPatchLevelFor(uid, "boot", isLong = true)
|
||||||
getCustomPatchLevelFor("boot", isLong = true)
|
return custom ?: getRealDevicePatchLevelInt("boot", isLong = true)
|
||||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = true)
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a custom patch level from the configuration if available.
|
* Retrieves the definitive device patch level integer for a given component. This function
|
||||||
|
* encapsulates the entire fallback chain and guarantees a non-null return.
|
||||||
*
|
*
|
||||||
|
* Fallback Priority:
|
||||||
|
* 1. Cached TEE attestation data.
|
||||||
|
* 2. Specific system property (e.g., ro.vendor.build.security_patch).
|
||||||
|
* 3. Default system patch level from Build.VERSION.SECURITY_PATCH.
|
||||||
|
*
|
||||||
|
* @param component The component ("system", "vendor", "boot").
|
||||||
|
* @param isLong Whether the final integer should be in YYYYMMDD format.
|
||||||
|
* @return The patch level as a guaranteed non-null Integer.
|
||||||
|
*/
|
||||||
|
private fun getRealDevicePatchLevelInt(component: String, isLong: Boolean): Int {
|
||||||
|
// Get value from cached TEE attestation data
|
||||||
|
DeviceAttestationService.CachedAttestationData?.let { data ->
|
||||||
|
val value =
|
||||||
|
when (component) {
|
||||||
|
"system" -> data.osPatchLevel
|
||||||
|
"vendor" -> data.vendorPatchLevel
|
||||||
|
"boot" -> data.bootPatchLevel
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (value != null) return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only check the specific vendor property, as the boot one is non-existent.
|
||||||
|
if (component == "vendor") {
|
||||||
|
val propValue = SystemProperties.get("ro.vendor.build.security_patch", "")
|
||||||
|
if (!propValue.isNullOrBlank()) {
|
||||||
|
parsePatchLevelValue(propValue, isLong)?.let { parsedValue ->
|
||||||
|
return parsedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a custom patch level from the configuration if available for a specific UID.
|
||||||
|
*
|
||||||
|
* @param uid The UID of the calling application.
|
||||||
* @param component The component to get the patch level for ("system", "vendor", "boot").
|
* @param component The component to get the patch level for ("system", "vendor", "boot").
|
||||||
* @param isLong Whether to return the patch level in `YYYYMMDD` or `YYYYMM` format.
|
* @param isLong Whether to return the patch level in `YYYYMMDD` or `YYYYMM` format.
|
||||||
* @return The custom patch level, or null if not configured.
|
* @return The custom patch level, or null if not configured.
|
||||||
*/
|
*/
|
||||||
private fun getCustomPatchLevelFor(component: String, isLong: Boolean): Int? {
|
private fun getCustomPatchLevelFor(uid: Int, component: String, isLong: Boolean): Int? {
|
||||||
val config = ConfigurationManager.customPatchLevelOverride ?: return null
|
val config = ConfigurationManager.getPatchLevelForUid(uid) ?: return null
|
||||||
val value =
|
val value =
|
||||||
when (component) {
|
when (component) {
|
||||||
"system" -> config.system ?: config.all
|
"system" -> config.system ?: config.all
|
||||||
@@ -137,11 +235,48 @@ object AndroidDeviceUtils {
|
|||||||
else -> config.all
|
else -> config.all
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
// "prop" or "no" indicates falling back to the system default.
|
// First, resolve dynamic keywords and templates into a concrete date string.
|
||||||
if (value.equals("no", ignoreCase = true) || value.equals("prop", ignoreCase = true)) {
|
val resolvedValue = resolveDateKeywords(value)
|
||||||
return null
|
|
||||||
|
return when {
|
||||||
|
// "device_default" indicates falling back to the system property.
|
||||||
|
resolvedValue.equals("device_default", ignoreCase = true) -> null
|
||||||
|
// "no" indicates this value should not be reported.
|
||||||
|
resolvedValue.equals("no", ignoreCase = true) -> DO_NOT_REPORT
|
||||||
|
// Otherwise, parse the resolved date string.
|
||||||
|
else -> parsePatchLevelValue(resolvedValue, isLong)
|
||||||
}
|
}
|
||||||
return parsePatchLevelValue(value, isLong)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves special date keywords and templates into a concrete "YYYY-MM-DD" date string.
|
||||||
|
*
|
||||||
|
* @param value The configuration value string (e.g., "today", "YYYY-MM-01").
|
||||||
|
* @return A concrete date string, or the original value if it's not a dynamic date keyword.
|
||||||
|
*/
|
||||||
|
private fun resolveDateKeywords(value: String): String {
|
||||||
|
// Handle the "today" keyword.
|
||||||
|
if (value.equals("today", ignoreCase = true)) {
|
||||||
|
return LocalDate.now().toString() // Returns "YYYY-MM-DD" format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle date templates like "YYYY-MM-01" or "2025-MM-DD".
|
||||||
|
if (
|
||||||
|
value.contains("YYYY", ignoreCase = true) ||
|
||||||
|
value.contains("MM", ignoreCase = true) ||
|
||||||
|
value.contains("DD", ignoreCase = true)
|
||||||
|
) {
|
||||||
|
|
||||||
|
val now = LocalDate.now()
|
||||||
|
// Chain replacements for YYYY, MM, and DD placeholders.
|
||||||
|
return value
|
||||||
|
.replace("YYYY", now.year.toString(), ignoreCase = true)
|
||||||
|
.replace("MM", String.format("%02d", now.monthValue), ignoreCase = true)
|
||||||
|
.replace("DD", String.format("%02d", now.dayOfMonth), ignoreCase = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not a dynamic keyword or template, return the original value.
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parses a patch level string (e.g., "2025-11-01") into an integer format. */
|
/** Parses a patch level string (e.g., "2025-11-01") into an integer format. */
|
||||||
@@ -205,17 +340,33 @@ object AndroidDeviceUtils {
|
|||||||
Build.VERSION_CODES.BAKLAVA to 400, // KeyMint 4.0
|
Build.VERSION_CODES.BAKLAVA to 400, // KeyMint 4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
val attestVersion: Int
|
/**
|
||||||
get() =
|
* Retrieves the attestation version based on security level and OS version. StrongBox (level 2)
|
||||||
DeviceAttestationService.CachedAttestationData?.attestVersion
|
* requires version 300.
|
||||||
?: attestVersionMap[Build.VERSION.SDK_INT]
|
*
|
||||||
?: 400 // Default to a recent version
|
* @param securityLevel The security level of the attestation (1 for TEE, 2 for StrongBox).
|
||||||
|
* @return The appropriate attestation version number.
|
||||||
|
*/
|
||||||
|
fun getAttestVersion(securityLevel: Int): Int {
|
||||||
|
// StrongBox security level requires an attestation version of at least 300.
|
||||||
|
if (securityLevel == SecurityLevel.STRONGBOX) {
|
||||||
|
return 300
|
||||||
|
}
|
||||||
|
return DeviceAttestationService.CachedAttestationData?.attestVersion
|
||||||
|
?: attestVersionMap[Build.VERSION.SDK_INT]
|
||||||
|
?: 400 // Default to a recent version
|
||||||
|
}
|
||||||
|
|
||||||
val keymasterVersion: Int
|
/**
|
||||||
get() =
|
* Retrieves the Keymaster/KeyMint version based on the attestation version.
|
||||||
DeviceAttestationService.CachedAttestationData?.keymasterVersion
|
*
|
||||||
?: if (attestVersion >= 100) attestVersion
|
* @param securityLevel The security level, used to determine the correct attestation version.
|
||||||
else 41 // Keymaster 4.1 for older versions
|
* @return The appropriate Keymaster or KeyMint version number.
|
||||||
|
*/
|
||||||
|
fun getKeymasterVersion(securityLevel: Int): Int {
|
||||||
|
val attestVersion = getAttestVersion(securityLevel)
|
||||||
|
return if (attestVersion >= 100) attestVersion else 41 // Keymaster 4.1 for older versions
|
||||||
|
}
|
||||||
|
|
||||||
// --- APEX and Module Hash Properties ---
|
// --- APEX and Module Hash Properties ---
|
||||||
|
|
||||||
@@ -229,11 +380,7 @@ object AndroidDeviceUtils {
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
pm?.getInstalledPackages(PackageManager.MATCH_APEX, 0)
|
pm?.getInstalledPackages(PackageManager.MATCH_APEX, 0)
|
||||||
}
|
}
|
||||||
packages
|
packages?.list.orEmpty().map { it.packageName to it.longVersionCode }
|
||||||
?.list
|
|
||||||
.orEmpty()
|
|
||||||
.map { it.packageName to it.longVersionCode }
|
|
||||||
.sortedBy { it.first }
|
|
||||||
}
|
}
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
SystemLogger.error("Failed to get APEX package information.", it)
|
SystemLogger.error("Failed to get APEX package information.", it)
|
||||||
@@ -242,17 +389,33 @@ object AndroidDeviceUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val moduleHash: ByteArray by lazy {
|
val moduleHash: ByteArray by lazy {
|
||||||
runCatching {
|
DeviceAttestationService.CachedAttestationData?.moduleHash
|
||||||
val encodables =
|
?: runCatching {
|
||||||
apexInfos.flatMap { (packageName, versionCode) ->
|
// TODO: figure out the correct calculation
|
||||||
listOf(DEROctetString(packageName.toByteArray()), ASN1Integer(versionCode))
|
val moduleSequences = ASN1EncodableVector()
|
||||||
|
|
||||||
|
// 1. Create a DERSequence for each module.
|
||||||
|
apexInfos.forEach { (packageName, versionCode) ->
|
||||||
|
val moduleVector = ASN1EncodableVector()
|
||||||
|
// Use explicit UTF-8 encoding for the package name.
|
||||||
|
moduleVector.add(DEROctetString(packageName.toByteArray(Charsets.UTF_8)))
|
||||||
|
moduleVector.add(ASN1Integer(versionCode))
|
||||||
|
moduleSequences.add(DERSequence(moduleVector))
|
||||||
}
|
}
|
||||||
val sequence = DERSequence(encodables.toTypedArray())
|
|
||||||
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
|
// 2. Create a DERSet. Bouncy Castle will automatically handle
|
||||||
}
|
// the sorting based on the DER-encoded value of each sequence.
|
||||||
.getOrElse {
|
val modulesSet = DERSet(moduleSequences)
|
||||||
SystemLogger.error("Failed to compute module hash.", it)
|
|
||||||
ByteArray(32) // Return empty hash on failure
|
// 3. Get the final DER-encoded byte array of the SET.
|
||||||
}
|
val encodedModules = modulesSet.encoded
|
||||||
|
|
||||||
|
// 4. Compute the SHA-256 hash.
|
||||||
|
MessageDigest.getInstance("SHA-256").digest(encodedModules)
|
||||||
|
}
|
||||||
|
.getOrElse {
|
||||||
|
SystemLogger.error("Failed to compute module hash.", it)
|
||||||
|
ByteArray(32) // Return empty hash on failure
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.13.1"
|
agp = "8.13.2"
|
||||||
annotation = "1.9.1"
|
annotation = "1.9.1"
|
||||||
jdk18on = "1.83"
|
jdk18on = "1.83"
|
||||||
kotlin = "2.2.21"
|
kotlin = "2.3.0"
|
||||||
ktfmt = "0.25.0"
|
ktfmt = "0.25.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|||||||
+21
-9
@@ -1,14 +1,26 @@
|
|||||||
🚀 **TEESimulator v2.1 Hotfix Release is Live!** 🚀
|
## 🎉 TEESimulator v3.1: Legacy Support & Resilience
|
||||||
|
|
||||||
This urgent hotfix addresses several critical issues identified in the previous v2.0 release.
|
This release marks a significant step forward in our mission, focusing on breathing life into devices with **broken TEEs** and extending full support to older Android versions (**Android 10–12**).
|
||||||
|
|
||||||
The v2.0 update, a significant refactoring effort, unfortunately introduced a few unexpected behaviors and bugs that we are now rectifying.
|
### 🛡️ Enhanced Keystore2 Emulation
|
||||||
|
We have implemented critical APIs to support devices where the hardware TEE is broken or for applications configured to use key generation mode. These improvements directly address detection vectors identified in v3.0:
|
||||||
|
|
||||||
**Key fixes in this release include:**
|
* **✅ Full Crypto Operations (`createOperation`)**: The simulator now correctly handles `SIGN`, `VERIFY`, `ENCRYPT`, and `DECRYPT` purposes for software-generated keys.
|
||||||
1. **Google Play Integrity:** Resolved an issue preventing the attainment of STRONG integrity for Google Play verdicts, caused by an incorrect vendor patch level format. ✅
|
* **🔗 Certificate Chain Updates (`updateSubcomponent`)**: Added support for applications updating the certificate chain of virtual keys (e.g., via `KeyStore.setKeyEntry`).
|
||||||
2. **Application Stability:** Fixed a critical crash related to an incorrect signature for the `SystemProperties.set` stub method. 🐛
|
* **📋 Enumeration Support (`listEntries`)**: Generated keys are now properly visible in enumeration APIs like `KeyStore.aliases()`, thanks to the implementation of `listEntries` and `listEntriesBatched`.
|
||||||
3. **Stealth Enhancement:** Implemented a fix to bypass detection by the `Android Native Detector`. 👻
|
|
||||||
|
|
||||||
🔬 We are actively investigating a recent detection method to further enhance stealth capabilities.
|
### 🔧 Compatibility & Stability
|
||||||
|
We’ve ironed out crashes and architecture-specific bugs to ensure a smooth experience across more devices:
|
||||||
|
|
||||||
🙏 Support for TEE-broken devices and Android 10/11 remains an area of ongoing improvement. We highly encourage you to submit any issues you encounter to help us refine these aspects! 🤝
|
* **Android 10**: Fixed a crash caused by the missing `waitForService` method.
|
||||||
|
* **Android 11**: Implemented environment initialization and daemon UID spoofing to successfully bypass keystore generation permission checks.
|
||||||
|
* **ARM 32-bit (Android 12)**: Resolved `ptrace` compatibility issues by falling back to `PTRACE_GETREGS` and `PTRACE_SETREGS`.
|
||||||
|
* **x86_64 Emulators**: Enforced respect for the stack pointer "red zone" and added a staging fallback mechanism for file descriptor transfering of `libTEESimulator.so`.
|
||||||
|
|
||||||
|
### 🚀 The Road Ahead
|
||||||
|
|
||||||
|
We are aware of the remaining detection vectors (see the issues list) and have clear solutions mapped out for the next release.
|
||||||
|
|
||||||
|
Google's aggressive push for **Remote Key Provisioning (RKP)** and the drying up of leaked keyboxes is **not** the end for TEESimulator. Our ultimate goal remains unchanged: defeating Keystore attestation **without relying on a valid keybox**.
|
||||||
|
|
||||||
|
We are inching closer to this milestone, but the fight for device freedom is complex and resource-intensive. Your patience and support (both time and financial) are vital as we conquer these new challenges.
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
allow keystore system_file unix_dgram_socket *
|
allow keystore {adb_data_file shell_data_file} file *
|
||||||
allow system_file keystore unix_dgram_socket *
|
|
||||||
allow keystore system_file file *
|
|
||||||
allow crash_dump keystore process *
|
allow crash_dump keystore process *
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "v2.1",
|
"version": "v3.1",
|
||||||
"versionCode": 20,
|
"versionCode": 59,
|
||||||
"zipUrl": "https://github.com/JingMatrix/TEESimulator/releases/download/v2.1/TEESimulator-v2.1-20-release.zip",
|
"zipUrl": "https://github.com/JingMatrix/TEESimulator/releases/download/v3.1/TEESimulator-v3.1-59-Release.zip",
|
||||||
"changelog": "https://raw.githubusercontent.com/JingMatrix/TEESimulator/main/module/changelog.md"
|
"changelog": "https://raw.githubusercontent.com/JingMatrix/TEESimulator/main/module/changelog.md"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,12 @@ public class ActivityThread {
|
|||||||
public static void initializeMainlineModules() {
|
public static void initializeMainlineModules() {
|
||||||
throw new UnsupportedOperationException("STUB!");
|
throw new UnsupportedOperationException("STUB!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ActivityThread systemMain() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContextImpl getSystemContext() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package android.app;
|
||||||
|
|
||||||
|
public class ContextImpl {
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package android.hardware.security.keymint;
|
||||||
|
|
||||||
|
public @interface BlockMode {
|
||||||
|
public static final int ECB = 1;
|
||||||
|
public static final int CBC = 2;
|
||||||
|
public static final int CTR = 3;
|
||||||
|
public static final int GCM = 32;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package android.hardware.security.keymint;
|
||||||
|
|
||||||
|
public @interface PaddingMode {
|
||||||
|
public static final int NONE = 1;
|
||||||
|
public static final int RSA_OAEP = 2;
|
||||||
|
public static final int RSA_PSS = 3;
|
||||||
|
public static final int RSA_PKCS1_1_5_ENCRYPT = 4;
|
||||||
|
public static final int RSA_PKCS1_1_5_SIGN = 5;
|
||||||
|
public static final int PKCS7 = 64;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class ExportResult implements Parcelable {
|
||||||
|
public final byte[] exportData;
|
||||||
|
public final int resultCode;
|
||||||
|
|
||||||
|
public ExportResult(int resultCode) {
|
||||||
|
this.resultCode = resultCode;
|
||||||
|
this.exportData = new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<ExportResult> CREATOR = new Creator<ExportResult>() {
|
||||||
|
@Override
|
||||||
|
public ExportResult createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExportResult[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class KeyCharacteristics implements Parcelable {
|
||||||
|
public KeymasterArguments hwEnforced;
|
||||||
|
public KeymasterArguments swEnforced;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<KeyCharacteristics> CREATOR = new Creator<KeyCharacteristics>() {
|
||||||
|
@Override
|
||||||
|
public KeyCharacteristics createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyCharacteristics[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
abstract class KeymasterArgument implements Parcelable {
|
||||||
|
public final int tag;
|
||||||
|
|
||||||
|
protected KeymasterArgument(int tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<KeymasterArgument> CREATOR = new Creator<KeymasterArgument>() {
|
||||||
|
@Override
|
||||||
|
public KeymasterArgument createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeymasterArgument[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class KeymasterArguments implements Parcelable {
|
||||||
|
|
||||||
|
private static final long UINT32_RANGE = 1L << 32;
|
||||||
|
public static final long UINT32_MAX_VALUE = UINT32_RANGE - 1;
|
||||||
|
|
||||||
|
private static final BigInteger UINT64_RANGE = BigInteger.ONE.shiftLeft(64);
|
||||||
|
public static final BigInteger UINT64_MAX_VALUE = UINT64_RANGE.subtract(BigInteger.ONE);
|
||||||
|
|
||||||
|
private List<KeymasterArgument> mArguments;
|
||||||
|
|
||||||
|
public static final @NonNull Parcelable.Creator<KeymasterArguments> CREATOR = new Parcelable.Creator<KeymasterArguments>() {
|
||||||
|
@Override
|
||||||
|
public KeymasterArguments createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeymasterArguments[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public KeymasterArguments() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeymasterArguments(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEnum(int tag, int value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEnums(int tag, int... values) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEnum(int tag, int defaultValue) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getEnums(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEnumTag(int tag, int value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getEnumTagValue(KeymasterArgument arg) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUnsignedInt(int tag, long value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUnsignedInt(int tag, long defaultValue) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUnsignedLong(int tag, BigInteger value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BigInteger> getUnsignedLongs(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLongTag(int tag, BigInteger value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigInteger getLongTagValue(KeymasterArgument arg) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addBoolean(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addBytes(int tag, byte[] value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes(int tag, byte[] defaultValue) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDate(int tag, Date value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDateIfNotNull(int tag, Date value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDate(int tag, Date defaultValue) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeymasterArgument getArgumentByTag(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsTag(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigInteger toUint64(long value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class KeymasterCertificateChain implements Parcelable {
|
||||||
|
private List<byte[]> mCertificates;
|
||||||
|
|
||||||
|
public KeymasterCertificateChain() {
|
||||||
|
this.mCertificates = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeymasterCertificateChain(List<byte[]> mCertificates) {
|
||||||
|
this.mCertificates = mCertificates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<KeymasterCertificateChain> CREATOR = new Creator<KeymasterCertificateChain>() {
|
||||||
|
@Override
|
||||||
|
public KeymasterCertificateChain createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeymasterCertificateChain[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class KeymasterDefs {
|
||||||
|
|
||||||
|
private KeymasterDefs() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag types.
|
||||||
|
public static final int KM_INVALID = 0 << 28;
|
||||||
|
public static final int KM_ENUM = 1 << 28;
|
||||||
|
public static final int KM_ENUM_REP = 2 << 28;
|
||||||
|
public static final int KM_UINT = 3 << 28;
|
||||||
|
public static final int KM_UINT_REP = 4 << 28;
|
||||||
|
public static final int KM_ULONG = 5 << 28;
|
||||||
|
public static final int KM_DATE = 6 << 28;
|
||||||
|
public static final int KM_BOOL = 7 << 28;
|
||||||
|
public static final int KM_BIGNUM = 8 << 28;
|
||||||
|
public static final int KM_BYTES = 9 << 28;
|
||||||
|
public static final int KM_ULONG_REP = 10 << 28;
|
||||||
|
|
||||||
|
// Tag values.
|
||||||
|
public static final int KM_TAG_INVALID = KM_INVALID | 0;
|
||||||
|
public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
|
||||||
|
public static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
|
||||||
|
public static final int KM_TAG_KEY_SIZE = KM_UINT | 3;
|
||||||
|
public static final int KM_TAG_BLOCK_MODE = KM_ENUM_REP | 4;
|
||||||
|
public static final int KM_TAG_DIGEST = KM_ENUM_REP | 5;
|
||||||
|
public static final int KM_TAG_PADDING = KM_ENUM_REP | 6;
|
||||||
|
public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 7;
|
||||||
|
public static final int KM_TAG_MIN_MAC_LENGTH = KM_UINT | 8;
|
||||||
|
|
||||||
|
public static final int KM_TAG_RESCOPING_ADD = KM_ENUM_REP | 101;
|
||||||
|
public static final int KM_TAG_RESCOPING_DEL = KM_ENUM_REP | 102;
|
||||||
|
public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
|
||||||
|
|
||||||
|
public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
|
||||||
|
public static final int KM_TAG_INCLUDE_UNIQUE_ID = KM_BOOL | 202;
|
||||||
|
|
||||||
|
public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
|
||||||
|
public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
|
||||||
|
public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
|
||||||
|
public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS = KM_UINT | 403;
|
||||||
|
public static final int KM_TAG_MAX_USES_PER_BOOT = KM_UINT | 404;
|
||||||
|
|
||||||
|
public static final int KM_TAG_ALL_USERS = KM_BOOL | 500;
|
||||||
|
public static final int KM_TAG_USER_ID = KM_UINT | 501;
|
||||||
|
public static final int KM_TAG_USER_SECURE_ID = KM_ULONG_REP | 502;
|
||||||
|
public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503;
|
||||||
|
public static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504;
|
||||||
|
public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
|
||||||
|
public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
|
||||||
|
public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507;
|
||||||
|
public static final int KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = KM_BOOL | 508;
|
||||||
|
public static final int KM_TAG_UNLOCKED_DEVICE_REQUIRED = KM_BOOL | 509;
|
||||||
|
|
||||||
|
public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
|
||||||
|
public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
|
||||||
|
|
||||||
|
public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
|
||||||
|
public static final int KM_TAG_ORIGIN = KM_ENUM | 702;
|
||||||
|
public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
|
||||||
|
public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
|
||||||
|
public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
|
||||||
|
public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_BRAND = KM_BYTES | 710;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_DEVICE = KM_BYTES | 711;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_PRODUCT = KM_BYTES | 712;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_SERIAL = KM_BYTES | 713;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_IMEI = KM_BYTES | 714;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_MANUFACTURER = KM_BYTES | 716;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_MODEL = KM_BYTES | 717;
|
||||||
|
public static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION = KM_BOOL | 720;
|
||||||
|
|
||||||
|
public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
|
||||||
|
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
|
||||||
|
public static final int KM_TAG_AUTH_TOKEN = KM_BYTES | 1002;
|
||||||
|
public static final int KM_TAG_MAC_LENGTH = KM_UINT | 1003;
|
||||||
|
|
||||||
|
// Algorithm values.
|
||||||
|
public static final int KM_ALGORITHM_RSA = 1;
|
||||||
|
public static final int KM_ALGORITHM_EC = 3;
|
||||||
|
public static final int KM_ALGORITHM_AES = 32;
|
||||||
|
public static final int KM_ALGORITHM_3DES = 33;
|
||||||
|
public static final int KM_ALGORITHM_HMAC = 128;
|
||||||
|
|
||||||
|
// Block modes.
|
||||||
|
public static final int KM_MODE_ECB = 1;
|
||||||
|
public static final int KM_MODE_CBC = 2;
|
||||||
|
public static final int KM_MODE_CTR = 3;
|
||||||
|
public static final int KM_MODE_GCM = 32;
|
||||||
|
|
||||||
|
// Padding modes.
|
||||||
|
public static final int KM_PAD_NONE = 1;
|
||||||
|
public static final int KM_PAD_RSA_OAEP = 2;
|
||||||
|
public static final int KM_PAD_RSA_PSS = 3;
|
||||||
|
public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
|
||||||
|
public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
|
||||||
|
public static final int KM_PAD_PKCS7 = 64;
|
||||||
|
|
||||||
|
// Digest modes.
|
||||||
|
public static final int KM_DIGEST_NONE = 0;
|
||||||
|
public static final int KM_DIGEST_MD5 = 1;
|
||||||
|
public static final int KM_DIGEST_SHA1 = 2;
|
||||||
|
public static final int KM_DIGEST_SHA_2_224 = 3;
|
||||||
|
public static final int KM_DIGEST_SHA_2_256 = 4;
|
||||||
|
public static final int KM_DIGEST_SHA_2_384 = 5;
|
||||||
|
public static final int KM_DIGEST_SHA_2_512 = 6;
|
||||||
|
|
||||||
|
// Key origins.
|
||||||
|
public static final int KM_ORIGIN_GENERATED = 0;
|
||||||
|
public static final int KM_ORIGIN_IMPORTED = 2;
|
||||||
|
public static final int KM_ORIGIN_UNKNOWN = 3;
|
||||||
|
public static final int KM_ORIGIN_SECURELY_IMPORTED = 4;
|
||||||
|
|
||||||
|
// Key usability requirements.
|
||||||
|
public static final int KM_BLOB_STANDALONE = 0;
|
||||||
|
public static final int KM_BLOB_REQUIRES_FILE_SYSTEM = 1;
|
||||||
|
|
||||||
|
// Operation Purposes.
|
||||||
|
public static final int KM_PURPOSE_ENCRYPT = 0;
|
||||||
|
public static final int KM_PURPOSE_DECRYPT = 1;
|
||||||
|
public static final int KM_PURPOSE_SIGN = 2;
|
||||||
|
public static final int KM_PURPOSE_VERIFY = 3;
|
||||||
|
public static final int KM_PURPOSE_WRAP = 5;
|
||||||
|
|
||||||
|
// Key formats.
|
||||||
|
public static final int KM_KEY_FORMAT_X509 = 0;
|
||||||
|
public static final int KM_KEY_FORMAT_PKCS8 = 1;
|
||||||
|
public static final int KM_KEY_FORMAT_RAW = 3;
|
||||||
|
|
||||||
|
// User authenticators.
|
||||||
|
public static final int HW_AUTH_PASSWORD = 1 << 0;
|
||||||
|
public static final int HW_AUTH_BIOMETRIC = 1 << 1;
|
||||||
|
|
||||||
|
// Error codes.
|
||||||
|
public static final int KM_ERROR_OK = 0;
|
||||||
|
public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_PURPOSE = -2;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_PURPOSE = -3;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_ALGORITHM = -4;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_ALGORITHM = -5;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_DIGEST = -13;
|
||||||
|
public static final int KM_ERROR_INVALID_EXPIRATION_TIME = -14;
|
||||||
|
public static final int KM_ERROR_INVALID_USER_ID = -15;
|
||||||
|
public static final int KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT = -16;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_KEY_FORMAT = -17;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_KEY_FORMAT = -18;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20;
|
||||||
|
public static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
|
||||||
|
public static final int KM_ERROR_KEY_EXPORT_OPTIONS_INVALID = -22;
|
||||||
|
public static final int KM_ERROR_DELEGATION_NOT_ALLOWED = -23;
|
||||||
|
public static final int KM_ERROR_KEY_NOT_YET_VALID = -24;
|
||||||
|
public static final int KM_ERROR_KEY_EXPIRED = -25;
|
||||||
|
public static final int KM_ERROR_KEY_USER_NOT_AUTHENTICATED = -26;
|
||||||
|
public static final int KM_ERROR_OUTPUT_PARAMETER_NULL = -27;
|
||||||
|
public static final int KM_ERROR_INVALID_OPERATION_HANDLE = -28;
|
||||||
|
public static final int KM_ERROR_INSUFFICIENT_BUFFER_SPACE = -29;
|
||||||
|
public static final int KM_ERROR_VERIFICATION_FAILED = -30;
|
||||||
|
public static final int KM_ERROR_TOO_MANY_OPERATIONS = -31;
|
||||||
|
public static final int KM_ERROR_UNEXPECTED_NULL_POINTER = -32;
|
||||||
|
public static final int KM_ERROR_INVALID_KEY_BLOB = -33;
|
||||||
|
public static final int KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED = -34;
|
||||||
|
public static final int KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED = -35;
|
||||||
|
public static final int KM_ERROR_IMPORTED_KEY_NOT_SIGNED = -36;
|
||||||
|
public static final int KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED = -37;
|
||||||
|
public static final int KM_ERROR_INVALID_ARGUMENT = -38;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_TAG = -39;
|
||||||
|
public static final int KM_ERROR_INVALID_TAG = -40;
|
||||||
|
public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41;
|
||||||
|
public static final int KM_ERROR_INVALID_RESCOPING = -42;
|
||||||
|
public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44;
|
||||||
|
public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45;
|
||||||
|
public static final int KM_ERROR_OPERATION_CANCELLED = -46;
|
||||||
|
public static final int KM_ERROR_CONCURRENT_ACCESS_CONFLICT = -47;
|
||||||
|
public static final int KM_ERROR_SECURE_HW_BUSY = -48;
|
||||||
|
public static final int KM_ERROR_SECURE_HW_COMMUNICATION_FAILED = -49;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_EC_FIELD = -50;
|
||||||
|
public static final int KM_ERROR_MISSING_NONCE = -51;
|
||||||
|
public static final int KM_ERROR_INVALID_NONCE = -52;
|
||||||
|
public static final int KM_ERROR_MISSING_MAC_LENGTH = -53;
|
||||||
|
public static final int KM_ERROR_KEY_RATE_LIMIT_EXCEEDED = -54;
|
||||||
|
public static final int KM_ERROR_CALLER_NONCE_PROHIBITED = -55;
|
||||||
|
public static final int KM_ERROR_KEY_MAX_OPS_EXCEEDED = -56;
|
||||||
|
public static final int KM_ERROR_INVALID_MAC_LENGTH = -57;
|
||||||
|
public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59;
|
||||||
|
public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66;
|
||||||
|
public static final int KM_ERROR_DEVICE_LOCKED = -72;
|
||||||
|
public static final int KM_ERROR_UNIMPLEMENTED = -100;
|
||||||
|
public static final int KM_ERROR_VERSION_MISMATCH = -101;
|
||||||
|
public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
|
||||||
|
|
||||||
|
public static final Map<Integer, String> sErrorCodeToString = new HashMap<Integer, String>();
|
||||||
|
static {
|
||||||
|
sErrorCodeToString.put(KM_ERROR_OK, "OK");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PURPOSE, "Unsupported purpose");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PURPOSE, "Incompatible purpose");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_ALGORITHM, "Unsupported algorithm");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_ALGORITHM, "Incompatible algorithm");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_MAC_LENGTH,
|
||||||
|
"Unsupported MAC or authentication tag length");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_DIGEST, "Incompatible digest");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_EXPIRATION_TIME, "Invalid expiration time");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_USER_ID, "Invalid user ID");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT,
|
||||||
|
"Invalid user authorization timeout");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_FORMAT, "Unsupported key format");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_KEY_FORMAT, "Incompatible key format");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_INPUT_LENGTH, "Invalid input length");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_KEY_NOT_YET_VALID, "Key not yet valid");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_KEY_EXPIRED, "Key expired");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_KEY_USER_NOT_AUTHENTICATED, "Key user not authenticated");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_OPERATION_HANDLE, "Invalid operation handle");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_VERIFICATION_FAILED, "Signature/MAC verification failed");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_TOO_MANY_OPERATIONS, "Too many operations");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_KEY_BLOB, "Invalid key blob");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_ARGUMENT, "Invalid argument");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_MISSING_NONCE, "Required IV missing");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_NONCE, "Invalid IV");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_CALLER_NONCE_PROHIBITED,
|
||||||
|
"Caller-provided IV not permitted");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH,
|
||||||
|
"Invalid MAC or authentication tag length");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_DEVICE_LOCKED, "Device locked");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getTagType(int tag) {
|
||||||
|
return tag & (0xF << 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getErrorMessage(int errorCode) {
|
||||||
|
String result = sErrorCodeToString.get(errorCode);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return String.valueOf(errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.security.keymaster.KeymasterCertificateChain;
|
||||||
|
|
||||||
|
public interface IKeystoreCertificateChainCallback {
|
||||||
|
void onFinished(KeystoreResponse keystoreResponse, KeymasterCertificateChain keymasterCertificateChain)
|
||||||
|
throws RemoteException;
|
||||||
|
|
||||||
|
public static abstract class Stub {
|
||||||
|
public static IKeystoreCertificateChainCallback asInterface(IBinder b) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.security.keymaster.ExportResult;
|
||||||
|
|
||||||
|
public interface IKeystoreExportKeyCallback {
|
||||||
|
void onFinished(ExportResult exportResult) throws RemoteException;
|
||||||
|
|
||||||
|
public static abstract class Stub {
|
||||||
|
public static IKeystoreExportKeyCallback asInterface(IBinder b) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.IInterface;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.security.keymaster.KeyCharacteristics;
|
||||||
|
|
||||||
|
public interface IKeystoreKeyCharacteristicsCallback extends IInterface {
|
||||||
|
void onFinished(KeystoreResponse keystoreResponse, KeyCharacteristics keyCharacteristics) throws RemoteException;
|
||||||
|
|
||||||
|
public static abstract class Stub {
|
||||||
|
public static IKeystoreKeyCharacteristicsCallback asInterface(IBinder b) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package android.security.keystore;
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import java.lang.String;
|
||||||
|
|
||||||
public interface IKeystoreService {
|
public interface IKeystoreService {
|
||||||
|
public static final String DESCRIPTOR = "android.security.keystore.IKeystoreService";
|
||||||
|
|
||||||
class Stub {
|
class Stub {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class KeystoreResponse implements Parcelable {
|
||||||
|
public final int error_code_;
|
||||||
|
public final String error_msg_;
|
||||||
|
|
||||||
|
protected KeystoreResponse(int error_code, String error_msg) {
|
||||||
|
this.error_code_ = error_code;
|
||||||
|
this.error_msg_ = error_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<KeystoreResponse> CREATOR = new Creator<KeystoreResponse>() {
|
||||||
|
@Override
|
||||||
|
public KeystoreResponse createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeystoreResponse[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package android.system.keystore2;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class CreateOperationResponse implements Parcelable {
|
||||||
|
public IKeystoreOperation iOperation;
|
||||||
|
|
||||||
|
public OperationChallenge operationChallenge;
|
||||||
|
|
||||||
|
public KeyParameters parameters;
|
||||||
|
|
||||||
|
public byte[] upgradedBlob;
|
||||||
|
|
||||||
|
public static final Creator<CreateOperationResponse> CREATOR = new Creator<CreateOperationResponse>() {
|
||||||
|
@Override
|
||||||
|
public CreateOperationResponse createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CreateOperationResponse[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package android.system.keystore2;
|
||||||
|
|
||||||
|
public @interface Domain {
|
||||||
|
public static final int APP = 0;
|
||||||
|
public static final int GRANT = 1;
|
||||||
|
public static final int SELINUX = 2;
|
||||||
|
public static final int BLOB = 3;
|
||||||
|
public static final int KEY_ID = 4;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package android.system.keystore2;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IInterface;
|
||||||
|
|
||||||
|
public interface IKeystoreOperation extends IInterface {
|
||||||
|
public static final java.lang.String DESCRIPTOR = "android.system.keystore2.IKeystoreOperation";
|
||||||
|
|
||||||
|
public void updateAad(byte[] aadInput);
|
||||||
|
|
||||||
|
public byte[] update(byte[] input);
|
||||||
|
|
||||||
|
public byte[] finish(byte[] input, byte[] signature);
|
||||||
|
|
||||||
|
public void abort() throws android.os.RemoteException;
|
||||||
|
|
||||||
|
abstract class Stub extends Binder implements IKeystoreOperation {
|
||||||
|
public static IKeystoreOperation asInterface(IBinder b) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder asBinder() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAad(byte[] aadInput) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package android.system.keystore2;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.hardware.security.keymint.KeyParameter;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class KeyParameters implements Parcelable {
|
||||||
|
public KeyParameter[] keyParameter;
|
||||||
|
|
||||||
|
public static final Creator<KeyParameters> CREATOR = new Creator<KeyParameters>() {
|
||||||
|
@Override
|
||||||
|
public KeyParameters createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyParameters[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package android.system.keystore2;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class OperationChallenge implements Parcelable {
|
||||||
|
public long challenge = 0L;
|
||||||
|
|
||||||
|
public static final Creator<OperationChallenge> CREATOR = new Creator<OperationChallenge>() {
|
||||||
|
@Override
|
||||||
|
public OperationChallenge createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OperationChallenge[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user