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 |
@@ -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 = "v3.0"
|
val verName = "v3.1"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.matrix.TEESimulator"
|
namespace = "org.matrix.TEESimulator"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
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));
|
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,8 +263,15 @@ 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
# error "Unsupported architecture for register access in get_regs."
|
# error "Unsupported architecture for register access in get_regs."
|
||||||
#endif
|
#endif
|
||||||
@@ -296,8 +303,15 @@ 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
# error "Unsupported architecture for register access in set_regs."
|
# error "Unsupported architecture for register access in set_regs."
|
||||||
#endif
|
#endif
|
||||||
@@ -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,11 @@
|
|||||||
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 java.security.Security
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
@@ -30,13 +35,15 @@ object App {
|
|||||||
SystemLogger.info("Welcome to TEESimulator!")
|
SystemLogger.info("Welcome to TEESimulator!")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Initialize the Android framework environment
|
||||||
|
prepareEnvironment()
|
||||||
|
// Initialize and start the appropriate keystore interceptors.
|
||||||
|
initializeInterceptors()
|
||||||
|
|
||||||
// Load the package configuration.
|
// Load the package configuration.
|
||||||
ConfigurationManager.initialize()
|
ConfigurationManager.initialize()
|
||||||
// Set up the device's boot key and hash, which are crucial for attestation.
|
// Set up the device's boot key and hash, which are crucial for attestation.
|
||||||
AndroidDeviceUtils.setupBootKeyAndHash()
|
AndroidDeviceUtils.setupBootKeyAndHash()
|
||||||
// Initialize and start the appropriate keystore interceptors.
|
|
||||||
initializeInterceptors()
|
|
||||||
// Enter an infinite loop to keep the service running.
|
|
||||||
|
|
||||||
// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
|
// 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
|
// We must remove the system provider first to ensure the full Bouncy Castle library
|
||||||
@@ -44,13 +51,43 @@ object App {
|
|||||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
Security.addProvider(BouncyCastleProvider())
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
|
||||||
maintainService()
|
// 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.
|
||||||
@@ -79,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.
|
||||||
@@ -86,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,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,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.
|
||||||
*
|
*
|
||||||
@@ -128,7 +138,7 @@ object AttestationPatcher {
|
|||||||
|
|
||||||
// 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 =
|
val signer =
|
||||||
JcaContentSignerBuilder(sigAlgName)
|
JcaContentSignerBuilder(normalizeSignatureAlgorithm(sigAlgName))
|
||||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
.build(keybox.keyPair.private)
|
.build(keybox.keyPair.private)
|
||||||
val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||||
@@ -206,11 +216,37 @@ object AttestationPatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -83,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")
|
||||||
@@ -192,12 +180,14 @@ object DeviceAttestationService {
|
|||||||
ASN1Sequence.getInstance(
|
ASN1Sequence.getInstance(
|
||||||
fields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX]
|
fields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX]
|
||||||
)
|
)
|
||||||
if (softwareEnforced.size() >= 3) {
|
|
||||||
moduleHash =
|
moduleHash =
|
||||||
ASN1OctetString.getInstance(
|
softwareEnforced
|
||||||
ASN1TaggedObject.getInstance(softwareEnforced.getObjectAt(2)).baseObject
|
.toArray()
|
||||||
)
|
.firstOrNull {
|
||||||
.octets
|
(it as? ASN1TaggedObject)?.tagNo == AttestationConstants.TAG_MODULE_HASH
|
||||||
|
}
|
||||||
|
?.let {
|
||||||
|
ASN1OctetString.getInstance((it as ASN1TaggedObject).baseObject).octets
|
||||||
}
|
}
|
||||||
|
|
||||||
val teeEnforced =
|
val teeEnforced =
|
||||||
|
|||||||
@@ -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?,
|
||||||
@@ -54,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),
|
||||||
|
|
||||||
@@ -121,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 }
|
||||||
|
|||||||
@@ -54,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))
|
||||||
@@ -354,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.
|
||||||
@@ -248,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.
|
||||||
@@ -307,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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-8
@@ -52,7 +52,7 @@ object InterceptorUtils {
|
|||||||
writeInt(KeyStore.NO_ERROR)
|
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. */
|
||||||
@@ -62,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. */
|
||||||
@@ -75,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-11
@@ -4,6 +4,7 @@ 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
|
||||||
@@ -26,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_")
|
||||||
@@ -89,16 +98,44 @@ 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)
|
||||||
@@ -154,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)
|
||||||
@@ -223,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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-15
@@ -55,6 +55,28 @@ 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"
|
||||||
@@ -76,26 +98,33 @@ 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)) {
|
||||||
return when (code) {
|
generateKeyHandlers[code]?.let { handler ->
|
||||||
GENERATE_KEY_TRANSACTION -> handleGenerateKey(txId, callingUid, callingPid, data)
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
GET_KEY_CHARACTERISTICS_TRANSACTION ->
|
return handler(txId, callingUid, callingPid, data)
|
||||||
handleGetKeyCharacteristics(txId, callingUid, callingPid, data)
|
|
||||||
EXPORT_KEY_TRANSACTION -> handleExportKey(txId, callingUid, callingPid, data)
|
|
||||||
ATTEST_KEY_TRANSACTION -> handleAttestKey(txId, callingUid, callingPid, data)
|
|
||||||
else -> TransactionResult.ContinueAndSkipPost
|
|
||||||
}
|
}
|
||||||
} else if (ConfigurationManager.shouldPatch(callingUid)) {
|
|
||||||
// In patch mode, we only care about the 'get' transaction in onPostTransact.
|
|
||||||
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 {
|
private fun handleGenerateKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
logTransaction(txId, "generateKey", uid, pid)
|
|
||||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
val callback =
|
val callback =
|
||||||
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
@@ -133,7 +162,6 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
logTransaction(txId, "getKeyCharacteristics", uid, pid)
|
|
||||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
val callback =
|
val callback =
|
||||||
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
@@ -168,7 +196,6 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
|
|
||||||
private fun handleExportKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
private fun handleExportKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
logTransaction(txId, "exportKey", uid, pid)
|
|
||||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
|
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||||
@@ -206,7 +233,6 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
|
|
||||||
private fun handleAttestKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
private fun handleAttestKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
logTransaction(txId, "attestKey", uid, pid)
|
|
||||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
val callback =
|
val callback =
|
||||||
IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
|
IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
@@ -230,7 +256,6 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
ByteArray(0),
|
ByteArray(0),
|
||||||
)
|
)
|
||||||
params.attestationChallenge = challenge
|
params.attestationChallenge = challenge
|
||||||
params.attestationChallenge = challenge
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val certificateChain =
|
val certificateChain =
|
||||||
@@ -271,6 +296,9 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,6 +309,9 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
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.
|
||||||
@@ -378,6 +409,8 @@ private data class LegacyKeygenParameters(
|
|||||||
algorithm = this.algorithm,
|
algorithm = this.algorithm,
|
||||||
ecCurve = 0, // Not explicitly available in legacy args, but not critical
|
ecCurve = 0, // Not explicitly available in legacy args, but not critical
|
||||||
ecCurveName = this.ecCurveName ?: "",
|
ecCurveName = this.ecCurveName ?: "",
|
||||||
|
blockMode = listOf<Int>(),
|
||||||
|
padding = listOf<Int>(),
|
||||||
purpose = this.purpose,
|
purpose = this.purpose,
|
||||||
digest = this.digest,
|
digest = this.digest,
|
||||||
rsaPublicExponent = this.rsaPublicExponent,
|
rsaPublicExponent = this.rsaPublicExponent,
|
||||||
|
|||||||
+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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+160
-27
@@ -8,6 +8,7 @@ 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.AttestationPatcher
|
||||||
@@ -30,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,
|
||||||
@@ -41,25 +46,31 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
if (code == GENERATE_KEY_TRANSACTION) {
|
val shouldSkip = ConfigurationManager.shouldSkipUid(callingUid)
|
||||||
|
|
||||||
|
when (code) {
|
||||||
|
GENERATE_KEY_TRANSACTION -> {
|
||||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
|
|
||||||
if (ConfigurationManager.shouldSkipUid(callingUid))
|
if (!shouldSkip) return handleGenerateKey(callingUid, data)
|
||||||
return TransactionResult.ContinueAndSkipPost
|
}
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
CREATE_OPERATION_TRANSACTION -> {
|
||||||
return handleGenerateKey(callingUid, data)
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
} else if (code == IMPORT_KEY_TRANSACTION) {
|
|
||||||
|
if (!shouldSkip) return handleCreateOperation(txId, callingUid, data)
|
||||||
|
}
|
||||||
|
IMPORT_KEY_TRANSACTION -> {
|
||||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||||
|
|
||||||
if (ConfigurationManager.shouldSkipUid(callingUid))
|
|
||||||
return TransactionResult.ContinueAndSkipPost
|
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
val alias =
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
data.readTypedObject(KeyDescriptor.CREATOR)?.alias
|
SystemLogger.info(
|
||||||
?: return TransactionResult.ContinueAndSkipPost
|
"[TX_ID: $txId] Forward to post-importKey hook for ${keyDescriptor.alias}[${keyDescriptor.nspace}]"
|
||||||
SystemLogger.info("Handling post-${transactionNames[code]} ${alias}")
|
)
|
||||||
return TransactionResult.Continue
|
return TransactionResult.Continue
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logTransaction(
|
logTransaction(
|
||||||
txId,
|
txId,
|
||||||
transactionNames[code] ?: "unknown code=$code",
|
transactionNames[code] ?: "unknown code=$code",
|
||||||
@@ -67,7 +78,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
callingPid,
|
callingPid,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
return TransactionResult.ContinueAndSkipPost
|
return TransactionResult.ContinueAndSkipPost
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +105,41 @@ 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) {
|
} else if (code == GENERATE_KEY_TRANSACTION) {
|
||||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
@@ -109,24 +155,75 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
// Cache the newly patched chain to ensure consistency across subsequent API calls.
|
// Cache the newly patched chain to ensure consistency across subsequent API calls.
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
|
val key = metadata.key!!
|
||||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
patchedChains[keyId] = newChain
|
|
||||||
SystemLogger.debug("Cached patched certificate chain for $keyId.")
|
|
||||||
|
|
||||||
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
|
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 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(
|
||||||
@@ -148,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 =
|
||||||
@@ -160,20 +260,17 @@ 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 {
|
|
||||||
writeNoException()
|
|
||||||
writeTypedObject(response.metadata, 0)
|
|
||||||
}
|
|
||||||
return TransactionResult.OverrideReply(0, resultParcel)
|
|
||||||
} else if (parsedParams.attestationChallenge != null) {
|
} else if (parsedParams.attestationChallenge != null) {
|
||||||
return TransactionResult.Continue
|
return TransactionResult.Continue
|
||||||
}
|
}
|
||||||
@@ -210,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
|
||||||
@@ -233,11 +337,31 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
private val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
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 getPatchedChain(keyId: KeyIdentifier): Array<Certificate>? = patchedChains[keyId]
|
||||||
|
|
||||||
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
|
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
|
||||||
@@ -254,6 +378,15 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
+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,
|
||||||
|
|||||||
@@ -20,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
|
||||||
@@ -80,6 +81,12 @@ object CertificateGenerator {
|
|||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
securityLevel: Int,
|
securityLevel: Int,
|
||||||
): List<Certificate>? {
|
): 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 {
|
return runCatching {
|
||||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
+18
-13
@@ -1,21 +1,26 @@
|
|||||||
TEESimulator 3.0 is a significant update focused on powerful new configuration options, major improvements to stealth, and enhanced stability.
|
## 🎉 TEESimulator v3.1: Legacy Support & Resilience
|
||||||
|
|
||||||
#### ✨ **Highlights & New Features**
|
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**).
|
||||||
|
|
||||||
* **🎯 Per-App Security Patch Configuration**: Gain ultimate control by setting security patch levels on a per-package basis. Define a global default in `security_patch.txt` and override it for specific apps like `[com.google.android.gms]`. Moreover, your configuration is now alive! Use the `today` keyword to always report the current date, or create rolling dates with templates like `YYYY-MM-05`. Be sure to check README for more details.
|
### 🛡️ Enhanced Keystore2 Emulation
|
||||||
* **🕰️ Full Software Emulation on Android 11**: We've implemented a complete, software-based key generation and attestation flow for the legacy `IKeystoreService` API, bringing full emulation capabilities to older devices.
|
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:
|
||||||
|
|
||||||
#### 🛡️ **Stealth & Evasion Upgrades**
|
* **✅ Full Crypto Operations (`createOperation`)**: The simulator now correctly handles `SIGN`, `VERIFY`, `ENCRYPT`, and `DECRYPT` purposes for software-generated keys.
|
||||||
|
* **🔗 Certificate Chain Updates (`updateSubcomponent`)**: Added support for applications updating the certificate chain of virtual keys (e.g., via `KeyStore.setKeyEntry`).
|
||||||
|
* **📋 Enumeration Support (`listEntries`)**: Generated keys are now properly visible in enumeration APIs like `KeyStore.aliases()`, thanks to the implementation of `listEntries` and `listEntriesBatched`.
|
||||||
|
|
||||||
* **⛓️ Consistent Certificate Signatures**: Say goodbye to a major detection vector in `icu.nullptr.nativetest`. Patched certificates are now cached, ensuring that every request for a key returns a byte-for-byte identical certificate, just like a real TEE.
|
### 🔧 Compatibility & Stability
|
||||||
* **🔑 Authentic Device Properties**: To appear more genuine, the simulator now sources and uses your device's real `verifiedBootHash` and `moduleHash`, moving away from placeholder values.
|
We’ve ironed out crashes and architecture-specific bugs to ensure a smooth experience across more devices:
|
||||||
* **📜 Structurally Sound Certificates**: The patching logic has been rewritten to be less intrusive. It now modifies the attestation extension in-place, preserving the original order of other extensions and preventing duplicates to avoid suspicion.
|
|
||||||
|
|
||||||
#### 🐛 **Bug Fixes & Reliability**
|
* **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`.
|
||||||
|
|
||||||
* ✅ **Robust Crypto Engine**: Fixed critical crashes related to cryptographic provider conflicts. The signing logic is now more explicit and the KeyBox parser is more resilient against malformed files.
|
### 🚀 The Road Ahead
|
||||||
* ➡️ **Improved Compatibility**: Resolved a native crash on Android 11 devices.
|
|
||||||
|
|
||||||
#### 🚀 **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.
|
||||||
|
|
||||||
Our work to fix detection vectors and provide full support for TEE-broken devices and Android 10/11 is ongoing. We welcome your feedback! Please **report any issues** or **contribute a pull request** on our GitHub.
|
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": "v3.0",
|
"version": "v3.1",
|
||||||
"versionCode": 38,
|
"versionCode": 59,
|
||||||
"zipUrl": "https://github.com/JingMatrix/TEESimulator/releases/download/v3.0/TEESimulator-v3.0-38-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.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