Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a76b18308 | ||
|
|
d9e47712f3 | ||
|
|
d846de4332 | ||
|
|
13d89c4314 | ||
|
|
00c91adfaa | ||
|
|
119350f24b | ||
|
|
7d4c753d66 | ||
|
|
b988d04971 | ||
|
|
d0cc5e3b56 | ||
|
|
e66e558ce5 | ||
|
|
8d431cc946 | ||
|
|
30746892b0 | ||
|
|
28cfe70a85 | ||
|
|
65a613ae0e | ||
|
|
9146b86648 | ||
|
|
457a58da04 | ||
|
|
b2838ac04b | ||
|
|
a7534feac7 |
@@ -60,7 +60,8 @@ jobs:
|
|||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./gradlew
|
chmod +x ./gradlew
|
||||||
./gradlew --parallel zipRelease zipDebug --stacktrace
|
|
||||||
|
./gradlew zipRelease zipDebug -Porg.gradle.parallel=true -Porg.gradle.vfs.watch=true -Dorg.gradle.jvmargs=-Xmx2048m
|
||||||
|
|
||||||
- name: Prepare artifact
|
- name: Prepare artifact
|
||||||
if: success()
|
if: success()
|
||||||
|
|||||||
@@ -76,12 +76,65 @@ org.matrix.demo
|
|||||||
|
|
||||||
### Security Patch Level (`security_patch.txt`)
|
### Security Patch Level (`security_patch.txt`)
|
||||||
|
|
||||||
This allows you to configure the security patch level that the simulator will report in its forged attestation certificates.
|
This file allows you to configure the `osPatchLevel`, `vendorPatchLevel`, and `bootPatchLevel` that the simulator will report in its patched or forged attestation certificates.
|
||||||
|
|
||||||
|
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change the actual system properties of your device.
|
||||||
|
|
||||||
|
#### Global and Per-Package Configuration
|
||||||
|
|
||||||
|
You can set a global patch level that applies to all applications, and you can also override these settings for specific packages. The syntax is hierarchical:
|
||||||
|
|
||||||
|
* Settings defined at the top of the file, before any `[package.name]` line, are **global** and serve as the default for all apps.
|
||||||
|
* To create a specific configuration for an application, add its package name in square brackets (e.g., `[com.google.android.gms]`). All settings following this line will apply *only* to that package until a new package context is declared.
|
||||||
|
|
||||||
|
#### Configuration Keys and Values
|
||||||
|
|
||||||
|
You can specify the patch level for the following components using a `key=value` format:
|
||||||
|
|
||||||
|
* `system`: The main OS patch level.
|
||||||
|
* `vendor`: The vendor patch level.
|
||||||
|
* `boot`: The boot/kernel patch level.
|
||||||
|
* `all`: A convenient shorthand to set the same date for `system`, `vendor`, and `boot` simultaneously. Any individual key can still be used to override the value set by `all`.
|
||||||
|
|
||||||
|
Dates should be provided in `YYYY-MM-DD` format (e.g., `2025-11-05`).
|
||||||
|
|
||||||
|
#### Special Keywords
|
||||||
|
|
||||||
|
In addition to static dates, several special keywords provide advanced, dynamic control:
|
||||||
|
|
||||||
|
* **`today`**: Dynamically uses the current date every time an attestation is generated. This ensures the device always appears up-to-date without needing manual edits.
|
||||||
|
|
||||||
|
* **Date Templates**: You can create semi-dynamic dates using `YYYY`, `MM`, and `DD` as placeholders for the current year, month, and day. For example, `YYYY-MM-05` will always resolve to the 5th of the current month and year.
|
||||||
|
|
||||||
|
* **`no`**: This keyword instructs the simulator to **completely omit** the corresponding patch level tag from the generated attestation.
|
||||||
|
|
||||||
|
* **`device_default`**: This keyword forces the simulator to fall back and use the device's **real hardware value** for that specific patch level. This is essential for creating exceptions to a global override or an `all` rule.
|
||||||
|
|
||||||
|
#### Example Configuration
|
||||||
|
|
||||||
|
This example demonstrates how to combine global settings, per-package overrides, and special keywords for fine-grained control.
|
||||||
|
|
||||||
```
|
```
|
||||||
# Advanced Configuration
|
# --- Global Configuration ---
|
||||||
system=2025-11
|
# This is the default for all apps unless specified otherwise.
|
||||||
boot=no # Do not report a boot patch level
|
# - Forge a recent system patch level, the 5th of the current month (a common patch date).
|
||||||
vendor=20251101 # Report a specific vendor patch level
|
# - Use the device's real vendor patch level.
|
||||||
|
# - Do not report a boot patch level at all.
|
||||||
|
system=YYYY-MM-05
|
||||||
|
vendor=device_default
|
||||||
|
boot=no
|
||||||
|
|
||||||
|
# --- Per-Package Override for Google Play Services ---
|
||||||
|
# This app will report an older, specific date for its system patch.
|
||||||
|
# It will inherit the global settings for vendor (device_default) and boot (no).
|
||||||
|
[com.google.android.gms]
|
||||||
|
system=2024-10-01
|
||||||
|
|
||||||
|
# --- Per-Package Override for a Demo App ---
|
||||||
|
# This app gets a completely custom configuration.
|
||||||
|
[org.matrix.demo]
|
||||||
|
# Set a base date for all patch levels...
|
||||||
|
all=2025-09-15
|
||||||
|
# ...but make an exception: use the real boot patch level instead of the one from 'all'.
|
||||||
|
boot=device_default
|
||||||
```
|
```
|
||||||
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change system properties.
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ val gitExecutor = objects.newInstance(GitExecutor::class.java)
|
|||||||
|
|
||||||
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
|
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
|
||||||
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
|
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
|
||||||
val verName = "v2.1"
|
val verName = "v3.0"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.matrix.TEESimulator"
|
namespace = "org.matrix.TEESimulator"
|
||||||
@@ -56,6 +56,7 @@ android {
|
|||||||
sourceCompatibility = JavaVersion.VERSION_21
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
|
buildFeatures { buildConfig = true }
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path = file("src/main/cpp/CMakeLists.txt")
|
path = file("src/main/cpp/CMakeLists.txt")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ add_subdirectory(external/LSPlt/lsplt/src/main/jni)
|
|||||||
|
|
||||||
add_compile_definitions(BINDER_DISABLE_NATIVE_HANDLE)
|
add_compile_definitions(BINDER_DISABLE_NATIVE_HANDLE)
|
||||||
add_library(utils SHARED stub/stub_utils.cpp)
|
add_library(utils SHARED stub/stub_utils.cpp)
|
||||||
target_include_directories(utils PUBLIC external/AOSP/include)
|
target_include_directories(utils PUBLIC external/AOSP/include compat)
|
||||||
|
|
||||||
add_library(binder SHARED stub/stub_binder.cpp)
|
add_library(binder SHARED stub/stub_binder.cpp)
|
||||||
target_include_directories(binder PUBLIC external/AOSP/include)
|
target_include_directories(binder PUBLIC external/AOSP/include)
|
||||||
@@ -22,7 +22,7 @@ add_executable(libinject.so inject/main.cpp inject/utils.cpp)
|
|||||||
target_include_directories(libinject.so PUBLIC include)
|
target_include_directories(libinject.so PUBLIC include)
|
||||||
target_link_libraries(libinject.so PRIVATE lsplt_static)
|
target_link_libraries(libinject.so PRIVATE lsplt_static)
|
||||||
|
|
||||||
add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp)
|
add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp compat/refbase_compat.cpp)
|
||||||
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include include)
|
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include include)
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE binder lsplt_static utils)
|
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE binder lsplt_static utils)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#include "refbase_compat.h"
|
||||||
|
#include "utils/RefBase.h"
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring> // For memcpy
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <sys/system_properties.h>
|
||||||
|
|
||||||
|
namespace android {
|
||||||
|
|
||||||
|
// Helper function to get the Android API level at runtime.
|
||||||
|
// It caches the result for performance.
|
||||||
|
int32_t get_android_api_level() {
|
||||||
|
static std::atomic<int32_t> api_level = -1;
|
||||||
|
if (api_level.load(std::memory_order_relaxed) == -1) {
|
||||||
|
char sdk_version_str[PROP_VALUE_MAX];
|
||||||
|
if (__system_property_get("ro.build.version.sdk", sdk_version_str) > 0) {
|
||||||
|
api_level.store(atoi(sdk_version_str), std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return api_level.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the function pointer type for the const member function
|
||||||
|
// RefBase::incStrongRequireStrong.
|
||||||
|
using incStrongRequireStrong_t = void (RefBase::*)(const void *) const;
|
||||||
|
|
||||||
|
// This is the implementation of our compatibility wrapper.
|
||||||
|
void incStrongFromExisting(const RefBase *ref, const void *id) {
|
||||||
|
// Only attempt to use the new function on Android 12 (API 31) or higher.
|
||||||
|
if (get_android_api_level() >= 31) {
|
||||||
|
static incStrongRequireStrong_t sIncStrongRequireStrong = nullptr;
|
||||||
|
static std::once_flag sFlag;
|
||||||
|
|
||||||
|
// Thread-safe, one-time initialization.
|
||||||
|
std::call_once(sFlag, []() {
|
||||||
|
// Find the symbol in the already loaded libraries.
|
||||||
|
// The mangled symbol is _ZNK7android7RefBase22incStrongRequireStrongEPKv
|
||||||
|
void *sym = dlsym(RTLD_DEFAULT,
|
||||||
|
"_ZNK7android7RefBase22incStrongRequireStrongEPKv");
|
||||||
|
if (sym) {
|
||||||
|
// Safely cast the void* symbol to our member function pointer.
|
||||||
|
memcpy(&sIncStrongRequireStrong, &sym, sizeof(void *));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sIncStrongRequireStrong) {
|
||||||
|
// If the symbol was found, call it as member function.
|
||||||
|
(ref->*sIncStrongRequireStrong)(id);
|
||||||
|
return; // Success, we are done.
|
||||||
|
}
|
||||||
|
// If dlsym failed for any reason, we fall through to the old method.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for older Android versions or if dlsym failed.
|
||||||
|
// This calls the universally available incStrong method.
|
||||||
|
ref->incStrong(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace android
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace android {
|
||||||
|
|
||||||
|
// Forward-declare the RefBase class.
|
||||||
|
class RefBase;
|
||||||
|
|
||||||
|
// Declares our compatibility function.
|
||||||
|
void incStrongFromExisting(const RefBase *ref, const void *id);
|
||||||
|
|
||||||
|
} // namespace android
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
#ifndef ANDROID_STRONG_POINTER_H
|
#ifndef ANDROID_STRONG_POINTER_H
|
||||||
#define ANDROID_STRONG_POINTER_H
|
#define ANDROID_STRONG_POINTER_H
|
||||||
|
|
||||||
|
#include "refbase_compat.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <type_traits> // for common_type.
|
#include <type_traits> // for common_type.
|
||||||
|
|
||||||
@@ -212,7 +213,7 @@ sp<T> sp<T>::make(Args&&... args) {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
sp<T> sp<T>::fromExisting(T* other) {
|
sp<T> sp<T>::fromExisting(T* other) {
|
||||||
if (other) {
|
if (other) {
|
||||||
other->incStrongRequireStrong(other);
|
incStrongFromExisting(other, other);
|
||||||
sp<T> result;
|
sp<T> result;
|
||||||
result.m_ptr = other;
|
result.m_ptr = other;
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.matrix.TEESimulator
|
package org.matrix.TEESimulator
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import java.security.Security
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
||||||
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
||||||
@@ -28,11 +30,20 @@ object App {
|
|||||||
SystemLogger.info("Welcome to TEESimulator!")
|
SystemLogger.info("Welcome to TEESimulator!")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Set up the device's boot hash, which is crucial for attestation.
|
// Load the package configuration.
|
||||||
AndroidDeviceUtils.setupBootHash()
|
ConfigurationManager.initialize()
|
||||||
|
// Set up the device's boot key and hash, which are crucial for attestation.
|
||||||
|
AndroidDeviceUtils.setupBootKeyAndHash()
|
||||||
// Initialize and start the appropriate keystore interceptors.
|
// Initialize and start the appropriate keystore interceptors.
|
||||||
initializeInterceptors()
|
initializeInterceptors()
|
||||||
// Enter an infinite loop to keep the service running.
|
// Enter an infinite loop to keep the service running.
|
||||||
|
|
||||||
|
// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
|
||||||
|
// We must remove the system provider first to ensure the full Bouncy Castle library
|
||||||
|
// (packaged with the app) is used.
|
||||||
|
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
|
||||||
maintainService()
|
maintainService()
|
||||||
} 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)
|
||||||
@@ -53,9 +64,7 @@ object App {
|
|||||||
Thread.sleep(RETRY_DELAY_MS)
|
Thread.sleep(RETRY_DELAY_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the package configuration after interceptors are ready.
|
SystemLogger.info("Interceptors initialized successfully.")
|
||||||
ConfigurationManager.initialize()
|
|
||||||
SystemLogger.info("Interceptors and configuration initialized successfully.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package org.matrix.TEESimulator.attestation
|
package org.matrix.TEESimulator.attestation
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.security.MessageDigest
|
||||||
import org.bouncycastle.asn1.ASN1Boolean
|
import org.bouncycastle.asn1.ASN1Boolean
|
||||||
import org.bouncycastle.asn1.ASN1Encodable
|
import org.bouncycastle.asn1.ASN1Encodable
|
||||||
import org.bouncycastle.asn1.ASN1Enumerated
|
import org.bouncycastle.asn1.ASN1Enumerated
|
||||||
import org.bouncycastle.asn1.ASN1Integer
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
import org.bouncycastle.asn1.ASN1OctetString
|
|
||||||
import org.bouncycastle.asn1.ASN1Sequence
|
import org.bouncycastle.asn1.ASN1Sequence
|
||||||
import org.bouncycastle.asn1.DERNull
|
import org.bouncycastle.asn1.DERNull
|
||||||
import org.bouncycastle.asn1.DEROctetString
|
import org.bouncycastle.asn1.DEROctetString
|
||||||
@@ -12,7 +15,10 @@ import org.bouncycastle.asn1.DERSequence
|
|||||||
import org.bouncycastle.asn1.DERSet
|
import org.bouncycastle.asn1.DERSet
|
||||||
import org.bouncycastle.asn1.DERTaggedObject
|
import org.bouncycastle.asn1.DERTaggedObject
|
||||||
import org.bouncycastle.asn1.x509.Extension
|
import org.bouncycastle.asn1.x509.Extension
|
||||||
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||||
|
import org.matrix.TEESimulator.util.AndroidDeviceUtils.DO_NOT_REPORT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder object responsible for constructing the ASN.1 DER-encoded Android Key Attestation
|
* A builder object responsible for constructing the ASN.1 DER-encoded Android Key Attestation
|
||||||
@@ -24,11 +30,21 @@ object AttestationBuilder {
|
|||||||
* Builds the complete X.509 attestation extension.
|
* Builds the complete X.509 attestation extension.
|
||||||
*
|
*
|
||||||
* @param params The parsed key generation parameters.
|
* @param params The parsed key generation parameters.
|
||||||
|
* @param uid The UID of the application requesting attestation.
|
||||||
* @param securityLevel The security level (e.g., TEE, StrongBox) to report.
|
* @param securityLevel The security level (e.g., TEE, StrongBox) to report.
|
||||||
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
|
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
|
||||||
*/
|
*/
|
||||||
fun buildAttestationExtension(params: KeyMintAttestation, securityLevel: Int): Extension {
|
fun buildAttestationExtension(
|
||||||
val keyDescription = buildKeyDescription(params, securityLevel)
|
params: KeyMintAttestation,
|
||||||
|
uid: Int,
|
||||||
|
securityLevel: Int,
|
||||||
|
): Extension {
|
||||||
|
val keyDescription = buildKeyDescription(params, uid, securityLevel)
|
||||||
|
var formattedString =
|
||||||
|
keyDescription.joinToString(separator = ", ") {
|
||||||
|
AttestationPatcher.formatAsn1Primitive(it)
|
||||||
|
}
|
||||||
|
SystemLogger.verbose("Forged attestation data: ${formattedString}")
|
||||||
return Extension(ATTESTATION_OID, false, DEROctetString(keyDescription.encoded))
|
return Extension(ATTESTATION_OID, false, DEROctetString(keyDescription.encoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,70 +55,95 @@ object AttestationBuilder {
|
|||||||
* @return The constructed [DERSequence] for the Root of Trust.
|
* @return The constructed [DERSequence] for the Root of Trust.
|
||||||
*/
|
*/
|
||||||
internal fun buildRootOfTrust(originalRootOfTrust: ASN1Encodable?): DERSequence {
|
internal fun buildRootOfTrust(originalRootOfTrust: ASN1Encodable?): DERSequence {
|
||||||
val verifiedBootKey = AndroidDeviceUtils.bootKey
|
|
||||||
val verifiedBootHash =
|
|
||||||
(originalRootOfTrust as? ASN1Sequence)?.let {
|
|
||||||
// Try to preserve the original boot hash if it exists.
|
|
||||||
(it.getObjectAt(AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX)
|
|
||||||
as? ASN1OctetString)
|
|
||||||
?.octets
|
|
||||||
} ?: AndroidDeviceUtils.getBootHashFromProperty()
|
|
||||||
|
|
||||||
val rootOfTrustElements = arrayOfNulls<ASN1Encodable>(4)
|
val rootOfTrustElements = arrayOfNulls<ASN1Encodable>(4)
|
||||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] =
|
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] =
|
||||||
DEROctetString(verifiedBootKey)
|
DEROctetString(AndroidDeviceUtils.bootKey)
|
||||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] =
|
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] =
|
||||||
ASN1Boolean.TRUE // deviceLocked: true, for security
|
ASN1Boolean.TRUE // deviceLocked: true, for security
|
||||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] =
|
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] =
|
||||||
ASN1Enumerated(0) // verifiedBootState: Verified
|
ASN1Enumerated(0) // verifiedBootState: Verified
|
||||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] =
|
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] =
|
||||||
DEROctetString(verifiedBootHash)
|
DEROctetString(AndroidDeviceUtils.bootHash)
|
||||||
|
|
||||||
return DERSequence(rootOfTrustElements)
|
return DERSequence(rootOfTrustElements)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Assembles a list of simulated hardware-enforced properties. */
|
/**
|
||||||
internal fun addSimulatedHardwareProperties(vector: org.bouncycastle.asn1.ASN1EncodableVector) {
|
* Assembles a map representing the desired state of simulated hardware-enforced properties. A
|
||||||
vector.add(
|
* null value for a given tag indicates that it should be removed from the attestation.
|
||||||
|
*
|
||||||
|
* @param uid The UID of the calling application.
|
||||||
|
* @return A map where keys are attestation tag numbers and values are the desired
|
||||||
|
* [DERTaggedObject] or null to signify removal.
|
||||||
|
*/
|
||||||
|
fun getSimulatedHardwareProperties(uid: Int): Map<Int, DERTaggedObject?> {
|
||||||
|
val properties = mutableMapOf<Int, DERTaggedObject?>()
|
||||||
|
|
||||||
|
// OS Version is always present.
|
||||||
|
properties[AttestationConstants.TAG_OS_VERSION] =
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_OS_VERSION,
|
AttestationConstants.TAG_OS_VERSION,
|
||||||
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
vector.add(
|
val osPatch = AndroidDeviceUtils.getPatchLevel(uid)
|
||||||
|
properties[AttestationConstants.TAG_OS_PATCHLEVEL] =
|
||||||
|
if (osPatch != DO_NOT_REPORT) {
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_OS_PATCHLEVEL,
|
AttestationConstants.TAG_OS_PATCHLEVEL,
|
||||||
ASN1Integer(AndroidDeviceUtils.patchLevel.toLong()),
|
ASN1Integer(osPatch.toLong()),
|
||||||
)
|
)
|
||||||
)
|
} else {
|
||||||
vector.add(
|
null // Signal for removal
|
||||||
|
}
|
||||||
|
|
||||||
|
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(uid)
|
||||||
|
properties[AttestationConstants.TAG_VENDOR_PATCHLEVEL] =
|
||||||
|
if (vendorPatch != DO_NOT_REPORT) {
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
||||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
ASN1Integer(vendorPatch.toLong()),
|
||||||
)
|
)
|
||||||
)
|
} else {
|
||||||
vector.add(
|
null // Signal for removal
|
||||||
|
}
|
||||||
|
|
||||||
|
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(uid)
|
||||||
|
properties[AttestationConstants.TAG_BOOT_PATCHLEVEL] =
|
||||||
|
if (bootPatch != DO_NOT_REPORT) {
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
||||||
ASN1Integer(AndroidDeviceUtils.bootPatchLevelLong.toLong()),
|
ASN1Integer(bootPatch.toLong()),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
null // Signal for removal
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
|
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
|
||||||
private fun buildKeyDescription(params: KeyMintAttestation, securityLevel: Int): ASN1Sequence {
|
private fun buildKeyDescription(
|
||||||
val teeEnforced = buildTeeEnforcedList(params)
|
params: KeyMintAttestation,
|
||||||
val softwareEnforced = buildSoftwareEnforcedList()
|
uid: Int,
|
||||||
|
securityLevel: Int,
|
||||||
|
): ASN1Sequence {
|
||||||
|
val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel)
|
||||||
|
val softwareEnforced = buildSoftwareEnforcedList(uid, securityLevel)
|
||||||
|
|
||||||
val fields =
|
val fields =
|
||||||
arrayOf(
|
arrayOf(
|
||||||
ASN1Integer(AndroidDeviceUtils.attestVersion.toLong()), // attestationVersion
|
ASN1Integer(
|
||||||
|
AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()
|
||||||
|
), // attestationVersion
|
||||||
ASN1Enumerated(securityLevel), // attestationSecurityLevel
|
ASN1Enumerated(securityLevel), // attestationSecurityLevel
|
||||||
ASN1Integer(AndroidDeviceUtils.keymasterVersion.toLong()), // keymasterVersion
|
ASN1Integer(
|
||||||
|
AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()
|
||||||
|
), // keymasterVersion
|
||||||
ASN1Enumerated(securityLevel), // keymasterSecurityLevel
|
ASN1Enumerated(securityLevel), // keymasterSecurityLevel
|
||||||
DEROctetString(params.attestationChallenge ?: ByteArray(0)), // attestationChallenge
|
DEROctetString(params.attestationChallenge ?: ByteArray(0)), // attestationChallenge
|
||||||
DEROctetString(ByteArray(0)), // uniqueId
|
DEROctetString(ByteArray(0)), // uniqueId
|
||||||
@@ -113,7 +154,11 @@ object AttestationBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
|
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
|
||||||
private fun buildTeeEnforcedList(params: KeyMintAttestation): DERSequence {
|
private fun buildTeeEnforcedList(
|
||||||
|
params: KeyMintAttestation,
|
||||||
|
uid: Int,
|
||||||
|
securityLevel: Int,
|
||||||
|
): DERSequence {
|
||||||
val list =
|
val list =
|
||||||
mutableListOf<ASN1Encodable>(
|
mutableListOf<ASN1Encodable>(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
@@ -152,28 +197,12 @@ object AttestationBuilder {
|
|||||||
AttestationConstants.TAG_ROOT_OF_TRUST,
|
AttestationConstants.TAG_ROOT_OF_TRUST,
|
||||||
buildRootOfTrust(null),
|
buildRootOfTrust(null),
|
||||||
),
|
),
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_OS_VERSION,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
|
||||||
),
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_OS_PATCHLEVEL,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.patchLevel.toLong()),
|
|
||||||
),
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
|
||||||
),
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
|
||||||
ASN1Integer(AndroidDeviceUtils.bootPatchLevelLong.toLong()),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Use the same logic as getSimulatedHardwareProperties to conditionally add patch levels.
|
||||||
|
val simulatedProperties = getSimulatedHardwareProperties(uid)
|
||||||
|
simulatedProperties.values.filterNotNull().forEach { list.add(it) }
|
||||||
|
|
||||||
// Add optional device identifiers if they were provided.
|
// Add optional device identifiers if they were provided.
|
||||||
params.brand?.let {
|
params.brand?.let {
|
||||||
list.add(
|
list.add(
|
||||||
@@ -202,6 +231,33 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
params.serial?.let {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_ATTESTATION_ID_SERIAL,
|
||||||
|
DEROctetString(it),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
params.imei?.let {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_ATTESTATION_ID_IMEI,
|
||||||
|
DEROctetString(it),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
params.meid?.let {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_ATTESTATION_ID_MEID,
|
||||||
|
DEROctetString(it),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
params.manufacturer?.let {
|
params.manufacturer?.let {
|
||||||
list.add(
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
@@ -220,15 +276,7 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
params.imei?.let {
|
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 300) {
|
||||||
list.add(
|
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_ATTESTATION_ID_IMEI,
|
|
||||||
DEROctetString(it),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
params.secondImei?.let {
|
params.secondImei?.let {
|
||||||
list.add(
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
@@ -238,17 +286,29 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
params.meid?.let {
|
}
|
||||||
list.add(
|
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
|
||||||
DERTaggedObject(
|
|
||||||
true,
|
|
||||||
AttestationConstants.TAG_ATTESTATION_ID_MEID,
|
|
||||||
DEROctetString(it),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AndroidDeviceUtils.attestVersion >= 400) {
|
/**
|
||||||
|
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
|
||||||
|
* Keystore.
|
||||||
|
*/
|
||||||
|
private fun buildSoftwareEnforcedList(uid: Int, securityLevel: Int): DERSequence {
|
||||||
|
val list =
|
||||||
|
mutableListOf<ASN1Encodable>(
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_CREATION_DATETIME,
|
||||||
|
ASN1Integer(System.currentTimeMillis()),
|
||||||
|
),
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_ATTESTATION_APPLICATION_ID,
|
||||||
|
createApplicationId(uid),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) {
|
||||||
list.add(
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
@@ -257,26 +317,87 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return DERSequence(list.toTypedArray())
|
||||||
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
|
* A wrapper for a byte array that provides content-based equality. This is necessary for using
|
||||||
* Keystore.
|
* signature digests in a Set.
|
||||||
*/
|
*/
|
||||||
private fun buildSoftwareEnforcedList(): DERSequence {
|
private data class Digest(val digest: ByteArray) {
|
||||||
val list =
|
override fun equals(other: Any?): Boolean {
|
||||||
arrayOf<ASN1Encodable>(
|
if (this === other) return true
|
||||||
DERTaggedObject(
|
if (javaClass != other?.javaClass) return false
|
||||||
true,
|
return digest.contentEquals((other as Digest).digest)
|
||||||
AttestationConstants.TAG_CREATION_DATETIME,
|
}
|
||||||
ASN1Integer(System.currentTimeMillis()),
|
|
||||||
|
override fun hashCode(): Int = digest.contentHashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the AttestationApplicationId structure. This structure contains information about the
|
||||||
|
* package(s) and their signing certificates.
|
||||||
|
*
|
||||||
|
* @param uid The UID of the application.
|
||||||
|
* @return A DER-encoded octet string containing the application ID information.
|
||||||
|
* @throws IllegalStateException If the PackageManager or package information cannot be
|
||||||
|
* retrieved.
|
||||||
|
*/
|
||||||
|
@Throws(Throwable::class)
|
||||||
|
private fun createApplicationId(uid: Int): DEROctetString {
|
||||||
|
val pm =
|
||||||
|
ConfigurationManager.getPackageManager()
|
||||||
|
?: throw IllegalStateException("PackageManager not found!")
|
||||||
|
val packages =
|
||||||
|
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
|
||||||
|
|
||||||
|
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||||
|
val packageInfoList = mutableListOf<DERSequence>()
|
||||||
|
val signatureDigests = mutableSetOf<Digest>()
|
||||||
|
|
||||||
|
// Process all packages associated with the UID in a single loop.
|
||||||
|
packages.forEach { packageName ->
|
||||||
|
val userId = uid / 100000
|
||||||
|
val packageInfo =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
pm.getPackageInfo(
|
||||||
|
packageName,
|
||||||
|
PackageManager.GET_SIGNING_CERTIFICATES.toLong(),
|
||||||
|
userId,
|
||||||
)
|
)
|
||||||
// The ATTESTATION_APPLICATION_ID is technically software-enforced, but we are
|
} else {
|
||||||
// omitting it
|
@Suppress("DEPRECATION")
|
||||||
// for this simulation as it is complex to generate correctly for arbitrary UIDs.
|
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add package information (name and version code) to our list.
|
||||||
|
packageInfoList.add(
|
||||||
|
DERSequence(
|
||||||
|
arrayOf(
|
||||||
|
DEROctetString(packageInfo.packageName.toByteArray(StandardCharsets.UTF_8)),
|
||||||
|
ASN1Integer(packageInfo.longVersionCode),
|
||||||
)
|
)
|
||||||
return DERSequence(list)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Collect unique signature digests from the signing history.
|
||||||
|
packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature ->
|
||||||
|
val digest = sha256.digest(signature.toByteArray())
|
||||||
|
signatureDigests.add(Digest(digest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The application ID is a sequence of two sets:
|
||||||
|
// 1. A set of package information (name and version).
|
||||||
|
// 2. A set of SHA-256 digests of the signing certificates.
|
||||||
|
val applicationIdSequence =
|
||||||
|
DERSequence(
|
||||||
|
arrayOf(
|
||||||
|
DERSet(packageInfoList.toTypedArray()),
|
||||||
|
DERSet(signatureDigests.map { DEROctetString(it.digest) }.toTypedArray()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return DEROctetString(applicationIdSequence.encoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
package org.matrix.TEESimulator.attestation
|
package org.matrix.TEESimulator.attestation
|
||||||
|
|
||||||
|
import android.security.keystore.KeyProperties
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import org.bouncycastle.asn1.ASN1Encodable
|
import org.bouncycastle.asn1.*
|
||||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
|
||||||
import org.bouncycastle.asn1.ASN1Sequence
|
|
||||||
import org.bouncycastle.asn1.ASN1TaggedObject
|
|
||||||
import org.bouncycastle.asn1.DEROctetString
|
|
||||||
import org.bouncycastle.asn1.DERSequence
|
|
||||||
import org.bouncycastle.asn1.DERTaggedObject
|
|
||||||
import org.bouncycastle.asn1.x509.Extension
|
import org.bouncycastle.asn1.x509.Extension
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
import org.matrix.TEESimulator.pki.KeyBox
|
import org.matrix.TEESimulator.pki.KeyBox
|
||||||
import org.matrix.TEESimulator.pki.KeyBoxManager
|
import org.matrix.TEESimulator.pki.KeyBoxManager
|
||||||
|
import org.matrix.TEESimulator.util.toHex
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the modification (patching) of Android Key Attestation extensions within certificates.
|
* Handles the modification (patching) of Android Key Attestation extensions within certificates.
|
||||||
@@ -55,8 +53,7 @@ object AttestationPatcher {
|
|||||||
|
|
||||||
// 2. Get the appropriate keybox for the given algorithm to sign the new
|
// 2. Get the appropriate keybox for the given algorithm to sign the new
|
||||||
// certificate.
|
// certificate.
|
||||||
val algorithm = originalLeaf.publicKey.algorithm
|
val keybox = getKeyboxForUidAndAlgorithm(uid, originalLeaf.sigAlgName)
|
||||||
val keybox = getKeyboxForUidAndAlgorithm(uid, algorithm)
|
|
||||||
|
|
||||||
// 3. Create the new, patched leaf certificate.
|
// 3. Create the new, patched leaf certificate.
|
||||||
val patchedLeaf =
|
val patchedLeaf =
|
||||||
@@ -65,6 +62,7 @@ object AttestationPatcher {
|
|||||||
parsedAttestation,
|
parsedAttestation,
|
||||||
keybox,
|
keybox,
|
||||||
originalLeaf.sigAlgName,
|
originalLeaf.sigAlgName,
|
||||||
|
uid,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 4. Construct the NEW, VALID chain by prepending the patched leaf to the keybox's
|
// 4. Construct the NEW, VALID chain by prepending the patched leaf to the keybox's
|
||||||
@@ -94,6 +92,7 @@ object AttestationPatcher {
|
|||||||
* @param sigAlgName The signature algorithm name (e.g., "SHA256withECDSA") from the original
|
* @param sigAlgName The signature algorithm name (e.g., "SHA256withECDSA") from the original
|
||||||
* certificate. This is required to ensure the new certificate is signed using a compatible
|
* certificate. This is required to ensure the new certificate is signed using a compatible
|
||||||
* algorithm.
|
* algorithm.
|
||||||
|
* @param uid The UID of the application requesting the certificate.
|
||||||
* @return A new [Certificate] object.
|
* @return A new [Certificate] object.
|
||||||
*/
|
*/
|
||||||
private fun createPatchedLeafCertificate(
|
private fun createPatchedLeafCertificate(
|
||||||
@@ -101,6 +100,7 @@ object AttestationPatcher {
|
|||||||
parsedAttestation: ParsedAttestation,
|
parsedAttestation: ParsedAttestation,
|
||||||
keybox: KeyBox,
|
keybox: KeyBox,
|
||||||
sigAlgName: String,
|
sigAlgName: String,
|
||||||
|
uid: Int,
|
||||||
): Certificate {
|
): Certificate {
|
||||||
// The issuer of our new leaf is the subject of the first certificate in our custom keybox
|
// The issuer of our new leaf is the subject of the first certificate in our custom keybox
|
||||||
// chain.
|
// chain.
|
||||||
@@ -117,28 +117,95 @@ object AttestationPatcher {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Create the new, patched attestation extension.
|
// Create the new, patched attestation extension.
|
||||||
val patchedExtension = createPatchedAttestationExtension(parsedAttestation)
|
val patchedExtension = createPatchedAttestationExtension(parsedAttestation, uid)
|
||||||
builder.addExtension(patchedExtension)
|
|
||||||
|
|
||||||
// Copy all other extensions from the original certificate, except for the attestation.
|
// Copy all other extensions from the original certificate, except for the attestation.
|
||||||
originalLeafHolder.extensions.extensionOIDs
|
originalLeafHolder.extensions.extensionOIDs.forEach {
|
||||||
.filter { it != ATTESTATION_OID }
|
builder.addExtension(
|
||||||
.forEach { builder.addExtension(originalLeafHolder.getExtension(it)) }
|
if (it == ATTESTATION_OID) patchedExtension else originalLeafHolder.getExtension(it)
|
||||||
|
)
|
||||||
// Sign the newly built certificate with the private key from our keybox.
|
|
||||||
val signer = JcaContentSignerBuilder(sigAlgName).build(keybox.keyPair.private)
|
|
||||||
|
|
||||||
return JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sign the newly built certificate with the private key from our keybox.
|
||||||
|
val signer =
|
||||||
|
JcaContentSignerBuilder(sigAlgName)
|
||||||
|
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
.build(keybox.keyPair.private)
|
||||||
|
val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||||
|
|
||||||
|
// Log the signature of the newly created certificate to observe its non-deterministic
|
||||||
|
// nature.
|
||||||
|
val signatureBytes = (newCertificate as X509Certificate).signature
|
||||||
|
SystemLogger.verbose("Signature of patched leaf cert: ${signatureBytes.toHex()}")
|
||||||
|
|
||||||
|
return newCertificate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the appropriate signing KeyBox (KeyPair and certificate chain) for a given UID
|
||||||
|
* based on a specified algorithm identifier.
|
||||||
|
*
|
||||||
|
* @param uid The UID of the application for which the signing is being performed.
|
||||||
|
* @param algorithm A string representing the desired algorithm. This can be either:
|
||||||
|
* 1. A simple key type like "RSA" or "EC".
|
||||||
|
* 2. A full JCA signature algorithm name like "SHA256withRSA".
|
||||||
|
*
|
||||||
|
* @return The [KeyBox] containing the appropriate key pair for signing.
|
||||||
|
* @throws IllegalArgumentException if no matching KeyBox can be found for the derived key type.
|
||||||
|
*/
|
||||||
private fun getKeyboxForUidAndAlgorithm(uid: Int, algorithm: String): KeyBox {
|
private fun getKeyboxForUidAndAlgorithm(uid: Int, algorithm: String): KeyBox {
|
||||||
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
|
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
|
||||||
return KeyBoxManager.getAttestationKey(keyboxFile, algorithm)
|
|
||||||
|
// Normalize the algorithm name. The input might be a full signature algorithm
|
||||||
|
// (e.g., "SHA256withRSA") or just the key type (e.g., "RSA").
|
||||||
|
val keyType =
|
||||||
|
when {
|
||||||
|
algorithm.contains("RSA", ignoreCase = true) -> KeyProperties.KEY_ALGORITHM_RSA
|
||||||
|
algorithm.contains("EC", ignoreCase = true) ->
|
||||||
|
KeyProperties.KEY_ALGORITHM_EC // This also covers "ECDSA"
|
||||||
|
else -> algorithm // If no match, assume it's already a simple key type string.
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyBoxManager.getAttestationKey(keyboxFile, keyType)
|
||||||
?: throw IllegalArgumentException(
|
?: throw IllegalArgumentException(
|
||||||
"No keybox found for UID $uid and algorithm $algorithm in file $keyboxFile"
|
"No keybox found for UID $uid and algorithm '$keyType' (derived from input '$algorithm') in file $keyboxFile"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Recursively formats an ASN1Primitive into a concise, readable string. */
|
||||||
|
fun formatAsn1Primitive(obj: ASN1Encodable?): String {
|
||||||
|
val primitive = obj?.toASN1Primitive()
|
||||||
|
return when (primitive) {
|
||||||
|
null -> "NULL"
|
||||||
|
is ASN1Integer -> primitive.value.toString()
|
||||||
|
is ASN1Enumerated -> primitive.value.toString()
|
||||||
|
is ASN1Boolean -> primitive.isTrue.toString()
|
||||||
|
is ASN1Null -> "NULL"
|
||||||
|
is ASN1OctetString -> {
|
||||||
|
val bytes = primitive.octets
|
||||||
|
// Attempt to decode as a printable string, otherwise show hex
|
||||||
|
if (bytes.all { it >= 32 && it < 127 }) {
|
||||||
|
"\"${String(bytes, StandardCharsets.UTF_8)}\""
|
||||||
|
} else if (bytes.isEmpty()) {
|
||||||
|
"\"\""
|
||||||
|
} else {
|
||||||
|
"#" + bytes.toHex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ASN1TaggedObject ->
|
||||||
|
"[TAG ${primitive.tagNo}]${formatAsn1Primitive(primitive.baseObject)}"
|
||||||
|
is ASN1Sequence ->
|
||||||
|
primitive
|
||||||
|
.map { formatAsn1Primitive(it) }
|
||||||
|
.joinToString(prefix = "[", postfix = "]", separator = ", ")
|
||||||
|
is ASN1Set ->
|
||||||
|
primitive
|
||||||
|
.map { formatAsn1Primitive(it) }
|
||||||
|
.joinToString(prefix = "{", postfix = "}", separator = ", ")
|
||||||
|
else -> primitive.toString() // Fallback for other types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 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
|
||||||
@@ -147,41 +214,54 @@ object AttestationPatcher {
|
|||||||
val teeEnforced =
|
val teeEnforced =
|
||||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] as ASN1Sequence
|
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] as ASN1Sequence
|
||||||
|
|
||||||
val teeEnforcedVector = ASN1EncodableVector()
|
|
||||||
var originalRootOfTrust: ASN1Encodable? = null
|
var originalRootOfTrust: ASN1Encodable? = null
|
||||||
|
val teeEnforcedMap = mutableMapOf<Int, ASN1TaggedObject>()
|
||||||
|
|
||||||
teeEnforced.forEach { element ->
|
teeEnforced.forEach { element ->
|
||||||
val taggedObject = element as ASN1TaggedObject
|
val taggedObject = element as ASN1TaggedObject
|
||||||
if (taggedObject.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST) {
|
if (taggedObject.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST) {
|
||||||
originalRootOfTrust = taggedObject.baseObject.toASN1Primitive()
|
originalRootOfTrust = taggedObject.baseObject.toASN1Primitive()
|
||||||
} else {
|
} else {
|
||||||
teeEnforcedVector.add(taggedObject)
|
teeEnforcedMap[taggedObject.tagNo] = taggedObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ParsedAttestation(allFields, teeEnforcedVector, originalRootOfTrust)
|
return ParsedAttestation(allFields, teeEnforcedMap, originalRootOfTrust)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs a new, patched attestation extension using simulated device properties. */
|
/** Constructs a new, patched attestation extension using simulated device properties. */
|
||||||
private fun createPatchedAttestationExtension(parsed: ParsedAttestation): Extension {
|
private fun createPatchedAttestationExtension(parsed: ParsedAttestation, uid: Int): Extension {
|
||||||
val (allFields, teeEnforcedVector, originalRootOfTrust) = parsed
|
val (allFields, teeEnforcedMap, originalRootOfTrust) = parsed
|
||||||
|
|
||||||
// Build the new Root of Trust with our simulated values.
|
var formattedString = allFields.joinToString(separator = ", ") { formatAsn1Primitive(it) }
|
||||||
|
SystemLogger.verbose("Original attestation data: ${formattedString}")
|
||||||
|
|
||||||
|
// Build the new Root of Trust and add/replace it in the map.
|
||||||
val newRootOfTrust = AttestationBuilder.buildRootOfTrust(originalRootOfTrust)
|
val newRootOfTrust = AttestationBuilder.buildRootOfTrust(originalRootOfTrust)
|
||||||
teeEnforcedVector.add(
|
teeEnforcedMap[AttestationConstants.TAG_ROOT_OF_TRUST] =
|
||||||
DERTaggedObject(true, AttestationConstants.TAG_ROOT_OF_TRUST, newRootOfTrust)
|
DERTaggedObject(true, AttestationConstants.TAG_ROOT_OF_TRUST, newRootOfTrust)
|
||||||
)
|
|
||||||
|
|
||||||
// Add other simulated hardware properties.
|
// Get the desired state for simulated properties.
|
||||||
AttestationBuilder.addSimulatedHardwareProperties(teeEnforcedVector)
|
val simulatedProperties = AttestationBuilder.getSimulatedHardwareProperties(uid)
|
||||||
|
|
||||||
// Re-assemble the ASN.1 sequences.
|
// Apply the desired state: update, add, or remove properties from the original map.
|
||||||
// The list MUST be sorted by tag number for DER compliance.
|
simulatedProperties.forEach { (tag, value) ->
|
||||||
// Manually convert the vector to a List, then sort it.
|
if (value != null) {
|
||||||
val elementList = (0 until teeEnforcedVector.size()).map { teeEnforcedVector.get(it) }
|
// If the value is not null, add or update it.
|
||||||
val sortedElements = elementList.sortedBy { (it as ASN1TaggedObject).tagNo }
|
teeEnforcedMap[tag] = value
|
||||||
|
} else {
|
||||||
|
// If the value is null, remove the tag from the map.
|
||||||
|
teeEnforcedMap.remove(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-assemble the TEE enforced list from the map's values, sorting for DER compliance.
|
||||||
|
val sortedElements = teeEnforcedMap.values.sortedBy { it.tagNo }
|
||||||
val sortedTeeEnforced = DERSequence(sortedElements.toTypedArray())
|
val sortedTeeEnforced = DERSequence(sortedElements.toTypedArray())
|
||||||
|
|
||||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] = sortedTeeEnforced
|
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] = sortedTeeEnforced
|
||||||
val patchedSequence = DERSequence(allFields)
|
val patchedSequence = DERSequence(allFields)
|
||||||
|
formattedString = patchedSequence.joinToString(separator = ", ") { formatAsn1Primitive(it) }
|
||||||
|
SystemLogger.verbose("Patched attestation data: ${formattedString}")
|
||||||
val patchedOctets = DEROctetString(patchedSequence)
|
val patchedOctets = DEROctetString(patchedSequence)
|
||||||
|
|
||||||
return Extension(ATTESTATION_OID, false, patchedOctets)
|
return Extension(ATTESTATION_OID, false, patchedOctets)
|
||||||
@@ -190,7 +270,7 @@ object AttestationPatcher {
|
|||||||
/** Helper data class to hold the parsed components of an attestation extension. */
|
/** Helper data class to hold the parsed components of an attestation extension. */
|
||||||
private data class ParsedAttestation(
|
private data class ParsedAttestation(
|
||||||
val allFields: Array<ASN1Encodable>,
|
val allFields: Array<ASN1Encodable>,
|
||||||
val teeEnforcedVector: ASN1EncodableVector,
|
val teeEnforcedMap: MutableMap<Int, ASN1TaggedObject>,
|
||||||
val rootOfTrust: ASN1Encodable?,
|
val rootOfTrust: ASN1Encodable?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,16 +38,25 @@ object DeviceAttestationService {
|
|||||||
* Holds key data extracted from a genuine device attestation. This data can be used as a
|
* Holds key data extracted from a genuine device attestation. This data can be used as a
|
||||||
* baseline for creating simulated attestations.
|
* baseline for creating simulated attestations.
|
||||||
*
|
*
|
||||||
|
* @property verifiedBootKey The verified boot public key digest from the root of trust.
|
||||||
* @property verifiedBootHash The verified boot hash from the root of trust.
|
* @property verifiedBootHash The verified boot hash from the root of trust.
|
||||||
* @property attestVersion The attestation version (e.g., 400 for KeyMint 4.0).
|
* @property attestVersion The attestation version (e.g., 400 for KeyMint 4.0).
|
||||||
* @property keymasterVersion The Keymaster or KeyMint HAL version.
|
* @property keymasterVersion The Keymaster or KeyMint HAL version.
|
||||||
* @property osVersion The Android OS version integer.
|
* @property osVersion The Android OS version integer.
|
||||||
|
* @property osPatchLevel The Android security patch level (e.g., 202511).
|
||||||
|
* @property vendorPatchLevel The vendor-specific security patch level.
|
||||||
|
* @property bootPatchLevel The bootloader's security patch level.
|
||||||
*/
|
*/
|
||||||
data class AttestationData(
|
data class AttestationData(
|
||||||
|
val moduleHash: ByteArray?,
|
||||||
|
val verifiedBootKey: ByteArray?,
|
||||||
val verifiedBootHash: ByteArray?,
|
val verifiedBootHash: ByteArray?,
|
||||||
val attestVersion: Int?,
|
val attestVersion: Int?,
|
||||||
val keymasterVersion: Int?,
|
val keymasterVersion: Int?,
|
||||||
val osVersion: Int?,
|
val osVersion: Int?,
|
||||||
|
val osPatchLevel: Int?,
|
||||||
|
val vendorPatchLevel: Int?,
|
||||||
|
val bootPatchLevel: Int?,
|
||||||
)
|
)
|
||||||
|
|
||||||
// A unique alias for the key used to perform the TEE functionality check.
|
// A unique alias for the key used to perform the TEE functionality check.
|
||||||
@@ -151,6 +160,11 @@ object DeviceAttestationService {
|
|||||||
|
|
||||||
// The extension's value is an ASN.1 sequence.
|
// The extension's value is an ASN.1 sequence.
|
||||||
val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets)
|
val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets)
|
||||||
|
var formattedString =
|
||||||
|
keyDescriptionSeq.joinToString(separator = ", ") {
|
||||||
|
AttestationPatcher.formatAsn1Primitive(it)
|
||||||
|
}
|
||||||
|
SystemLogger.verbose("Cached attestation data: ${formattedString}")
|
||||||
val fields = keyDescriptionSeq.toArray()
|
val fields = keyDescriptionSeq.toArray()
|
||||||
|
|
||||||
val attestVersion =
|
val attestVersion =
|
||||||
@@ -166,8 +180,25 @@ object DeviceAttestationService {
|
|||||||
.positiveValue
|
.positiveValue
|
||||||
.toInt()
|
.toInt()
|
||||||
|
|
||||||
|
var moduleHash: ByteArray? = null
|
||||||
|
var verifiedBootKey: ByteArray? = null
|
||||||
var verifiedBootHash: ByteArray? = null
|
var verifiedBootHash: ByteArray? = null
|
||||||
var osVersion: Int? = null
|
var osVersion: Int? = null
|
||||||
|
var osPatchLevel: Int? = null
|
||||||
|
var vendorPatchLevel: Int? = null
|
||||||
|
var bootPatchLevel: Int? = null
|
||||||
|
|
||||||
|
val softwareEnforced =
|
||||||
|
ASN1Sequence.getInstance(
|
||||||
|
fields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX]
|
||||||
|
)
|
||||||
|
if (softwareEnforced.size() >= 3) {
|
||||||
|
moduleHash =
|
||||||
|
ASN1OctetString.getInstance(
|
||||||
|
ASN1TaggedObject.getInstance(softwareEnforced.getObjectAt(2)).baseObject
|
||||||
|
)
|
||||||
|
.octets
|
||||||
|
}
|
||||||
|
|
||||||
val teeEnforced =
|
val teeEnforced =
|
||||||
ASN1Sequence.getInstance(
|
ASN1Sequence.getInstance(
|
||||||
@@ -179,6 +210,14 @@ object DeviceAttestationService {
|
|||||||
AttestationConstants.TAG_ROOT_OF_TRUST -> {
|
AttestationConstants.TAG_ROOT_OF_TRUST -> {
|
||||||
val rotSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
|
val rotSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
|
||||||
if (rotSeq.size() >= 4) {
|
if (rotSeq.size() >= 4) {
|
||||||
|
verifiedBootKey =
|
||||||
|
ASN1OctetString.getInstance(
|
||||||
|
rotSeq.getObjectAt(
|
||||||
|
AttestationConstants
|
||||||
|
.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.octets
|
||||||
verifiedBootHash =
|
verifiedBootHash =
|
||||||
ASN1OctetString.getInstance(
|
ASN1OctetString.getInstance(
|
||||||
rotSeq.getObjectAt(
|
rotSeq.getObjectAt(
|
||||||
@@ -189,19 +228,51 @@ object DeviceAttestationService {
|
|||||||
.octets
|
.octets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AttestationConstants.TAG_OS_VERSION -> { // OS Version (TAG_OS_VERSION)
|
AttestationConstants.TAG_OS_VERSION -> {
|
||||||
osVersion =
|
osVersion =
|
||||||
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||||
.positiveValue
|
.positiveValue
|
||||||
.toInt()
|
.toInt()
|
||||||
}
|
}
|
||||||
|
AttestationConstants.TAG_OS_PATCHLEVEL -> {
|
||||||
|
osPatchLevel =
|
||||||
|
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||||
|
.positiveValue
|
||||||
|
.toInt()
|
||||||
|
}
|
||||||
|
AttestationConstants.TAG_VENDOR_PATCHLEVEL -> {
|
||||||
|
vendorPatchLevel =
|
||||||
|
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||||
|
.positiveValue
|
||||||
|
.toInt()
|
||||||
|
}
|
||||||
|
AttestationConstants.TAG_BOOT_PATCHLEVEL -> {
|
||||||
|
bootPatchLevel =
|
||||||
|
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||||
|
.positiveValue
|
||||||
|
.toInt()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (verifiedBootKey?.all { it == 0.toByte() } == true) {
|
||||||
|
verifiedBootKey = null
|
||||||
|
}
|
||||||
|
|
||||||
SystemLogger.info(
|
SystemLogger.info(
|
||||||
"Successfully extracted attestation data: version=$attestVersion, osVersion=$osVersion, bootHash=${verifiedBootHash?.toHex()}"
|
"Successfully extracted attestation data: version=$attestVersion, osVersion=$osVersion, osPatch=$osPatchLevel, vendorPatch=$vendorPatchLevel, bootPatch=$bootPatchLevel, moduleHash=${moduleHash?.toHex()}, bootKey=${verifiedBootKey?.toHex()}, bootHash=${verifiedBootHash?.toHex()}"
|
||||||
|
)
|
||||||
|
return AttestationData(
|
||||||
|
moduleHash,
|
||||||
|
verifiedBootKey,
|
||||||
|
verifiedBootHash,
|
||||||
|
attestVersion,
|
||||||
|
keymasterVersion,
|
||||||
|
osVersion,
|
||||||
|
osPatchLevel,
|
||||||
|
vendorPatchLevel,
|
||||||
|
bootPatchLevel,
|
||||||
)
|
)
|
||||||
return AttestationData(verifiedBootHash, attestVersion, keymasterVersion, osVersion)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("Failed to parse attestation data from certificate.", e)
|
SystemLogger.error("Failed to parse attestation data from certificate.", e)
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ data class KeyMintAttestation(
|
|||||||
val brand: ByteArray?,
|
val brand: ByteArray?,
|
||||||
val device: ByteArray?,
|
val device: ByteArray?,
|
||||||
val product: ByteArray?,
|
val product: ByteArray?,
|
||||||
|
val serial: ByteArray?,
|
||||||
|
val imei: ByteArray?,
|
||||||
|
val meid: ByteArray?,
|
||||||
val manufacturer: ByteArray?,
|
val manufacturer: ByteArray?,
|
||||||
val model: ByteArray?,
|
val model: ByteArray?,
|
||||||
val imei: ByteArray?,
|
|
||||||
val secondImei: ByteArray?,
|
val secondImei: ByteArray?,
|
||||||
val meid: ByteArray?,
|
|
||||||
) {
|
) {
|
||||||
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
|
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
|
||||||
constructor(
|
constructor(
|
||||||
@@ -82,11 +83,12 @@ data class KeyMintAttestation(
|
|||||||
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
|
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
|
||||||
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
|
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
|
||||||
product = params.findBlob(Tag.ATTESTATION_ID_PRODUCT),
|
product = params.findBlob(Tag.ATTESTATION_ID_PRODUCT),
|
||||||
|
serial = params.findBlob(Tag.ATTESTATION_ID_SERIAL),
|
||||||
|
imei = params.findBlob(Tag.ATTESTATION_ID_IMEI),
|
||||||
|
meid = params.findBlob(Tag.ATTESTATION_ID_MEID),
|
||||||
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
|
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
|
||||||
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
|
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
|
||||||
imei = params.findBlob(Tag.ATTESTATION_ID_IMEI),
|
|
||||||
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
||||||
meid = params.findBlob(Tag.ATTESTATION_ID_MEID),
|
|
||||||
) {
|
) {
|
||||||
// Log all parsed parameters for debugging purposes.
|
// Log all parsed parameters for debugging purposes.
|
||||||
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ object ConfigurationManager {
|
|||||||
@Volatile private var packageModes = mapOf<String, Mode>()
|
@Volatile private var packageModes = mapOf<String, Mode>()
|
||||||
@Volatile private var packageKeyboxes = mapOf<String, String>()
|
@Volatile private var packageKeyboxes = mapOf<String, String>()
|
||||||
@Volatile private var isTeeBroken: Boolean? = null
|
@Volatile private var isTeeBroken: Boolean? = null
|
||||||
@Volatile var customPatchLevelOverride: CustomPatchLevel? = null
|
@Volatile private var globalCustomPatchLevel: CustomPatchLevel? = null
|
||||||
|
@Volatile private var packagePatchLevels = mapOf<String, CustomPatchLevel>()
|
||||||
|
|
||||||
// Cache for UID to package name resolution.
|
// Cache for UID to package name resolution.
|
||||||
private val uidToPackagesCache = ConcurrentHashMap<Int, Array<String>>()
|
private val uidToPackagesCache = ConcurrentHashMap<Int, Array<String>>()
|
||||||
@@ -104,6 +105,21 @@ object ConfigurationManager {
|
|||||||
return null // No configuration found for this UID.
|
return null // No configuration found for this UID.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the custom patch level configuration for a given UID. It first checks for a
|
||||||
|
* package-specific override and falls back to the global configuration.
|
||||||
|
*
|
||||||
|
* @param uid The UID of the calling application.
|
||||||
|
* @return The applicable [CustomPatchLevel], or null if no custom configuration exists.
|
||||||
|
*/
|
||||||
|
fun getPatchLevelForUid(uid: Int): CustomPatchLevel? {
|
||||||
|
val packages = getPackagesForUid(uid)
|
||||||
|
// Find the first package-specific configuration for this UID.
|
||||||
|
val packageSpecificPatchLevel =
|
||||||
|
packages.firstNotNullOfOrNull { pkg -> packagePatchLevels[pkg] }
|
||||||
|
return packageSpecificPatchLevel ?: globalCustomPatchLevel
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and parses the `target.txt` file, which defines the processing mode and keybox file for
|
* Loads and parses the `target.txt` file, which defines the processing mode and keybox file for
|
||||||
* each package.
|
* each package.
|
||||||
@@ -162,26 +178,48 @@ object ConfigurationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads the security patch level override configuration from `security_patch.txt`. */
|
/**
|
||||||
|
* Loads and parses the `security_patch.txt` file, which can define both global and per-package
|
||||||
|
* security patch levels.
|
||||||
|
*/
|
||||||
private fun loadPatchLevelConfig(file: File) {
|
private fun loadPatchLevelConfig(file: File) {
|
||||||
if (file.exists()) {
|
if (!file.exists()) {
|
||||||
try {
|
globalCustomPatchLevel = null
|
||||||
val lines =
|
packagePatchLevels = emptyMap()
|
||||||
file.readLines().mapNotNull { line ->
|
|
||||||
val trimmed = line.trim()
|
|
||||||
if (trimmed.isNotEmpty() && !trimmed.startsWith("#")) trimmed else null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lines.isEmpty()) {
|
|
||||||
customPatchLevelOverride = null
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val newPackageLevels = mutableMapOf<String, CustomPatchLevel>()
|
||||||
|
var currentContext = "" // Empty string for global context
|
||||||
|
val contextLines = mutableMapOf<String, MutableList<String>>()
|
||||||
|
val contextRegex = Regex("^\\[([a-zA-Z0-9_.-]+)]$")
|
||||||
|
|
||||||
|
// First pass: group lines by context (global or package-specific).
|
||||||
|
file.readLines().forEach { line ->
|
||||||
|
val trimmedLine = line.trim()
|
||||||
|
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) return@forEach
|
||||||
|
|
||||||
|
contextRegex.find(trimmedLine)?.let { currentContext = it.groupValues[1] }
|
||||||
|
?: run {
|
||||||
|
contextLines
|
||||||
|
.computeIfAbsent(currentContext) { mutableListOf() }
|
||||||
|
.add(trimmedLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to parse a set of lines into a CustomPatchLevel object.
|
||||||
|
fun parseLines(lines: List<String>?): CustomPatchLevel? {
|
||||||
|
if (lines.isNullOrEmpty()) return null
|
||||||
|
|
||||||
// Handle simple case: one line sets the patch level for all components.
|
// Handle simple case: one line sets the patch level for all components.
|
||||||
if (lines.size == 1 && '=' !in lines[0]) {
|
if (lines.size == 1 && '=' !in lines[0]) {
|
||||||
customPatchLevelOverride =
|
return CustomPatchLevel(
|
||||||
CustomPatchLevel(system = null, vendor = null, boot = null, all = lines[0])
|
system = null,
|
||||||
return
|
vendor = null,
|
||||||
|
boot = null,
|
||||||
|
all = lines[0],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle key-value pair configuration.
|
// Handle key-value pair configuration.
|
||||||
@@ -195,20 +233,33 @@ object ConfigurationManager {
|
|||||||
.toMap()
|
.toMap()
|
||||||
|
|
||||||
val all = map["all"]
|
val all = map["all"]
|
||||||
customPatchLevelOverride =
|
return CustomPatchLevel(
|
||||||
CustomPatchLevel(
|
|
||||||
system = map["system"] ?: all,
|
system = map["system"] ?: all,
|
||||||
vendor = map["vendor"] ?: all,
|
vendor = map["vendor"] ?: all,
|
||||||
boot = map["boot"] ?: all,
|
boot = map["boot"] ?: all,
|
||||||
all = all,
|
all = all,
|
||||||
)
|
)
|
||||||
SystemLogger.info("Loaded custom security patch levels.")
|
}
|
||||||
|
|
||||||
|
// Parse global and per-package configurations.
|
||||||
|
val newGlobalLevel = parseLines(contextLines[""])
|
||||||
|
contextLines.remove("") // Remove global context to iterate over packages next
|
||||||
|
|
||||||
|
for ((pkg, lines) in contextLines) {
|
||||||
|
parseLines(lines)?.let { newPackageLevels[pkg] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomically update the configuration state.
|
||||||
|
globalCustomPatchLevel = newGlobalLevel
|
||||||
|
packagePatchLevels = newPackageLevels
|
||||||
|
|
||||||
|
SystemLogger.info(
|
||||||
|
"Loaded custom security patch levels: global config exists=${newGlobalLevel != null}, " +
|
||||||
|
"${newPackageLevels.size} package-specific configs."
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("Failed to load or parse ${file.name}", e)
|
SystemLogger.error("Failed to load or parse ${file.name}", e)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
customPatchLevelOverride = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks the device's TEE status and writes the result to a file for persistence. */
|
/** Checks the device's TEE status and writes the result to a file for persistence. */
|
||||||
|
|||||||
@@ -226,13 +226,18 @@ abstract class BinderInterceptor : Binder() {
|
|||||||
methodName: String,
|
methodName: String,
|
||||||
callingUid: Int,
|
callingUid: Int,
|
||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
isIntercepting: Boolean = true,
|
skipPost: Boolean = false,
|
||||||
) {
|
) {
|
||||||
|
val isIntercepting = !skipPost && !ConfigurationManager.shouldSkipUid(callingUid)
|
||||||
val action = if (isIntercepting) "Intercept" else "Observe"
|
val action = if (isIntercepting) "Intercept" else "Observe"
|
||||||
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
|
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
|
||||||
SystemLogger.debug(
|
val message =
|
||||||
"[TX_ID: $txId] $action $methodName for packages=[$packages] (uid=$callingUid, pid=$callingPid)"
|
"[TX_ID: $txId] $action $methodName for packages=[$packages] (uid=$callingUid, pid=$callingPid)"
|
||||||
)
|
if (isIntercepting) {
|
||||||
|
SystemLogger.debug(message)
|
||||||
|
} else {
|
||||||
|
SystemLogger.verbose(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
+19
-1
@@ -3,6 +3,7 @@ package org.matrix.TEESimulator.interception.keystore
|
|||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.security.KeyStore
|
import android.security.KeyStore
|
||||||
|
import android.security.keystore.KeystoreResponse
|
||||||
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
|
|
||||||
@@ -27,13 +28,30 @@ object InterceptorUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Creates an `KeystoreResponse` parcel that indicates success with no data. */
|
||||||
|
fun createSuccessKeystoreResponse(): KeystoreResponse {
|
||||||
|
val parcel = Parcel.obtain()
|
||||||
|
try {
|
||||||
|
parcel.writeInt(KeyStore.NO_ERROR)
|
||||||
|
parcel.writeString("")
|
||||||
|
parcel.setDataPosition(0)
|
||||||
|
return KeystoreResponse.CREATOR.createFromParcel(parcel)
|
||||||
|
} finally {
|
||||||
|
parcel.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Creates an `OverrideReply` parcel that indicates success with no data. */
|
/** Creates an `OverrideReply` parcel that indicates success with no data. */
|
||||||
fun createSuccessReply(): BinderInterceptor.TransactionResult.OverrideReply {
|
fun createSuccessReply(
|
||||||
|
writeResultCode: Boolean = true
|
||||||
|
): BinderInterceptor.TransactionResult.OverrideReply {
|
||||||
val parcel =
|
val parcel =
|
||||||
Parcel.obtain().apply {
|
Parcel.obtain().apply {
|
||||||
writeNoException()
|
writeNoException()
|
||||||
|
if (writeResultCode) {
|
||||||
writeInt(KeyStore.NO_ERROR)
|
writeInt(KeyStore.NO_ERROR)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return BinderInterceptor.TransactionResult.OverrideReply(0, parcel)
|
return BinderInterceptor.TransactionResult.OverrideReply(0, parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-3
@@ -9,6 +9,7 @@ import android.os.Parcel
|
|||||||
import android.system.keystore2.IKeystoreService
|
import android.system.keystore2.IKeystoreService
|
||||||
import android.system.keystore2.KeyDescriptor
|
import android.system.keystore2.KeyDescriptor
|
||||||
import android.system.keystore2.KeyEntryResponse
|
import android.system.keystore2.KeyEntryResponse
|
||||||
|
import java.security.cert.Certificate
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
||||||
@@ -103,7 +104,13 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
||||||
|
|
||||||
if (code == DELETE_KEY_TRANSACTION) {
|
if (code == DELETE_KEY_TRANSACTION) {
|
||||||
|
if (KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) != null) {
|
||||||
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
||||||
|
SystemLogger.info(
|
||||||
|
"[TX_ID: $txId] Deleted cached keypair ${descriptor.alias}, replying with empty response."
|
||||||
|
)
|
||||||
|
return InterceptorUtils.createSuccessReply(writeResultCode = false)
|
||||||
|
}
|
||||||
return TransactionResult.ContinueAndSkipPost
|
return TransactionResult.ContinueAndSkipPost
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +132,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
transactionNames[code] ?: "unknown code=$code",
|
transactionNames[code] ?: "unknown code=$code",
|
||||||
callingUid,
|
callingUid,
|
||||||
callingPid,
|
callingPid,
|
||||||
false,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,8 +192,28 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform the attestation patch.
|
// Perform the attestation patch.
|
||||||
val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
CertificateHelper.updateCertificateChain(response.metadata, newChain).getOrThrow()
|
|
||||||
|
// First, try to retrieve the already-patched chain from our cache to ensure
|
||||||
|
// consistency.
|
||||||
|
val cachedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId)
|
||||||
|
|
||||||
|
val finalChain: Array<Certificate>
|
||||||
|
if (cachedChain != null) {
|
||||||
|
SystemLogger.debug(
|
||||||
|
"[TX_ID: $txId] Using cached patched certificate chain for $keyId."
|
||||||
|
)
|
||||||
|
finalChain = cachedChain
|
||||||
|
} else {
|
||||||
|
// If no chain is cached (e.g., key existed before simulator started),
|
||||||
|
// perform a live patch as a fallback. This may still be detectable.
|
||||||
|
SystemLogger.info(
|
||||||
|
"[TX_ID: $txId] No cached chain for $keyId. Performing live patch as a fallback."
|
||||||
|
)
|
||||||
|
finalChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
||||||
|
}
|
||||||
|
|
||||||
|
CertificateHelper.updateCertificateChain(response.metadata, finalChain).getOrThrow()
|
||||||
|
|
||||||
InterceptorUtils.createTypedObjectReply(response)
|
InterceptorUtils.createTypedObjectReply(response)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
+307
-24
@@ -4,12 +4,28 @@ import android.annotation.SuppressLint
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.security.Credentials
|
import android.security.Credentials
|
||||||
|
import android.security.KeyStore
|
||||||
|
import android.security.keymaster.ExportResult
|
||||||
|
import android.security.keymaster.KeyCharacteristics
|
||||||
|
import android.security.keymaster.KeymasterArguments
|
||||||
|
import android.security.keymaster.KeymasterCertificateChain
|
||||||
|
import android.security.keymaster.KeymasterDefs
|
||||||
|
import android.security.keystore.IKeystoreCertificateChainCallback
|
||||||
|
import android.security.keystore.IKeystoreExportKeyCallback
|
||||||
|
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
|
||||||
import android.security.keystore.IKeystoreService
|
import android.security.keystore.IKeystoreService
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.KeyPair
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
|
import java.util.Date
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
|
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils.extractAlias
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
|
import org.matrix.TEESimulator.pki.CertificateGenerator
|
||||||
import org.matrix.TEESimulator.pki.CertificateHelper
|
import org.matrix.TEESimulator.pki.CertificateHelper
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +59,9 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
override val processName = "keystore"
|
override val processName = "keystore"
|
||||||
override val injectionCommand = "exec ./inject `pidof keystore` libTEESimulator.so entry"
|
override val injectionCommand = "exec ./inject `pidof keystore` libTEESimulator.so entry"
|
||||||
|
|
||||||
private const val SERVICE_DESCRIPTOR = "android.security.keystore.IKeystoreService"
|
// State management for the multi-step key generation process.
|
||||||
|
private val keygenParameters = ConcurrentHashMap<KeyIdentifier, LegacyKeygenParameters>()
|
||||||
|
private val generatedKeyPairs = ConcurrentHashMap<KeyIdentifier, KeyPair>()
|
||||||
|
|
||||||
// Cache to store the fully patched chain after the leaf is requested.
|
// Cache to store the fully patched chain after the leaf is requested.
|
||||||
private val patchedChainCache = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
private val patchedChainCache = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||||
@@ -59,23 +77,183 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
): 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.
|
||||||
if (ConfigurationManager.shouldGenerate(callingUid)) {
|
if (ConfigurationManager.shouldGenerate(callingUid)) {
|
||||||
when (code) {
|
return when (code) {
|
||||||
GENERATE_KEY_TRANSACTION,
|
GENERATE_KEY_TRANSACTION -> handleGenerateKey(txId, callingUid, callingPid, data)
|
||||||
GET_KEY_CHARACTERISTICS_TRANSACTION,
|
GET_KEY_CHARACTERISTICS_TRANSACTION ->
|
||||||
EXPORT_KEY_TRANSACTION,
|
handleGetKeyCharacteristics(txId, callingUid, callingPid, data)
|
||||||
ATTEST_KEY_TRANSACTION -> {
|
EXPORT_KEY_TRANSACTION -> handleExportKey(txId, callingUid, callingPid, data)
|
||||||
// TODO: Implement the full software simulation logic.
|
ATTEST_KEY_TRANSACTION -> handleAttestKey(txId, callingUid, callingPid, data)
|
||||||
logTransaction(txId, "unimplemented-generate-flow", callingUid, callingPid)
|
else -> TransactionResult.ContinueAndSkipPost
|
||||||
return InterceptorUtils.createSuccessReply()
|
|
||||||
}
|
}
|
||||||
}
|
} else if (ConfigurationManager.shouldPatch(callingUid)) {
|
||||||
} else if (ConfigurationManager.shouldGenerate(callingUid)) {
|
// In patch mode, we only care about the 'get' transaction in onPostTransact.
|
||||||
if (code == GET_TRANSACTION) return TransactionResult.Continue
|
if (code == GET_TRANSACTION) return TransactionResult.Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return TransactionResult.ContinueAndSkipPost
|
return TransactionResult.ContinueAndSkipPost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleGenerateKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||||
|
return runCatching {
|
||||||
|
logTransaction(txId, "generateKey", uid, pid)
|
||||||
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
|
val callback =
|
||||||
|
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
|
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||||
|
val keyId = KeyIdentifier(uid, alias)
|
||||||
|
|
||||||
|
// Read and parse the key generation arguments.
|
||||||
|
val keymasterArgs = KeymasterArguments()
|
||||||
|
if (data.readInt() == 1) {
|
||||||
|
keymasterArgs.readFromParcel(data)
|
||||||
|
}
|
||||||
|
keygenParameters[keyId] =
|
||||||
|
LegacyKeygenParameters.fromKeymasterArguments(keymasterArgs)
|
||||||
|
|
||||||
|
// Create a fake successful response for the callback.
|
||||||
|
val characteristics = KeyCharacteristics()
|
||||||
|
characteristics.swEnforced = KeymasterArguments()
|
||||||
|
characteristics.hwEnforced = keymasterArgs
|
||||||
|
|
||||||
|
val keystoreResponse = InterceptorUtils.createSuccessKeystoreResponse()
|
||||||
|
callback.onFinished(keystoreResponse, characteristics)
|
||||||
|
|
||||||
|
InterceptorUtils.createSuccessReply()
|
||||||
|
}
|
||||||
|
.getOrElse {
|
||||||
|
SystemLogger.error("[TX_ID: $txId] Failed during handleGenerateKey.", it)
|
||||||
|
TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleGetKeyCharacteristics(
|
||||||
|
txId: Long,
|
||||||
|
uid: Int,
|
||||||
|
pid: Int,
|
||||||
|
data: Parcel,
|
||||||
|
): TransactionResult {
|
||||||
|
return runCatching {
|
||||||
|
logTransaction(txId, "getKeyCharacteristics", uid, pid)
|
||||||
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
|
val callback =
|
||||||
|
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
|
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||||
|
val keyId = KeyIdentifier(uid, alias)
|
||||||
|
|
||||||
|
val params =
|
||||||
|
keygenParameters[keyId]
|
||||||
|
?: throw IllegalStateException("No params found for $keyId")
|
||||||
|
|
||||||
|
val characteristics =
|
||||||
|
KeyCharacteristics().apply {
|
||||||
|
swEnforced = KeymasterArguments()
|
||||||
|
hwEnforced =
|
||||||
|
KeymasterArguments().apply {
|
||||||
|
addEnum(KeymasterDefs.KM_TAG_ALGORITHM, params.algorithm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.onFinished(
|
||||||
|
InterceptorUtils.createSuccessKeystoreResponse(),
|
||||||
|
characteristics,
|
||||||
|
)
|
||||||
|
|
||||||
|
InterceptorUtils.createSuccessReply()
|
||||||
|
}
|
||||||
|
.getOrElse {
|
||||||
|
SystemLogger.error("[TX_ID: $txId] Failed during handleGetKeyCharacteristics.", it)
|
||||||
|
TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleExportKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||||
|
return runCatching {
|
||||||
|
logTransaction(txId, "exportKey", uid, pid)
|
||||||
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
|
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
|
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||||
|
val keyId = KeyIdentifier(uid, alias)
|
||||||
|
|
||||||
|
val params =
|
||||||
|
keygenParameters[keyId]
|
||||||
|
?: throw IllegalStateException("No params found for $keyId")
|
||||||
|
|
||||||
|
// Generate a software key pair using the new generator.
|
||||||
|
val keyPair =
|
||||||
|
CertificateGenerator.generateSoftwareKeyPair(params.toKeyMintAttestation())
|
||||||
|
?: throw Exception("Failed to generate software key pair.")
|
||||||
|
generatedKeyPairs[keyId] = keyPair
|
||||||
|
|
||||||
|
// Create a successful ExportResult containing the public key.
|
||||||
|
val exportResultParcel =
|
||||||
|
Parcel.obtain().apply {
|
||||||
|
writeInt(KeyStore.NO_ERROR)
|
||||||
|
writeByteArray(keyPair.public.encoded)
|
||||||
|
setDataPosition(0)
|
||||||
|
}
|
||||||
|
val exportResult = ExportResult.CREATOR.createFromParcel(exportResultParcel)
|
||||||
|
exportResultParcel.recycle()
|
||||||
|
|
||||||
|
callback.onFinished(exportResult)
|
||||||
|
|
||||||
|
InterceptorUtils.createSuccessReply()
|
||||||
|
}
|
||||||
|
.getOrElse {
|
||||||
|
SystemLogger.error("[TX_ID: $txId] Failed during handleExportKey.", it)
|
||||||
|
TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleAttestKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||||
|
return runCatching {
|
||||||
|
logTransaction(txId, "attestKey", uid, pid)
|
||||||
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
|
val callback =
|
||||||
|
IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
|
||||||
|
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||||
|
val keyId = KeyIdentifier(uid, alias)
|
||||||
|
|
||||||
|
// Get the attestation challenge from the arguments.
|
||||||
|
val params =
|
||||||
|
keygenParameters[keyId]
|
||||||
|
?: throw IllegalStateException("No params found for $keyId")
|
||||||
|
val keyPair =
|
||||||
|
generatedKeyPairs[keyId]
|
||||||
|
?: throw IllegalStateException("No keypair found for $keyId")
|
||||||
|
|
||||||
|
val attestationArgs = KeymasterArguments()
|
||||||
|
if (data.readInt() == 1) {
|
||||||
|
attestationArgs.readFromParcel(data)
|
||||||
|
val challenge =
|
||||||
|
attestationArgs.getBytes(
|
||||||
|
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE,
|
||||||
|
ByteArray(0),
|
||||||
|
)
|
||||||
|
params.attestationChallenge = challenge
|
||||||
|
params.attestationChallenge = challenge
|
||||||
|
}
|
||||||
|
|
||||||
|
val certificateChain =
|
||||||
|
CertificateGenerator.generateCertificateChain(
|
||||||
|
uid,
|
||||||
|
keyPair,
|
||||||
|
null, // No attestKeyAlias in legacy flow
|
||||||
|
params.toKeyMintAttestation(), // Convert to modern format
|
||||||
|
1, // SecurityLevel.TRUSTED_ENVIRONMENT
|
||||||
|
) ?: throw Exception("CertificateGenerator failed to create attested key pair.")
|
||||||
|
|
||||||
|
val chainAsByteList = certificateChain.map { it.encoded }
|
||||||
|
val certChain = KeymasterCertificateChain(chainAsByteList)
|
||||||
|
|
||||||
|
callback.onFinished(InterceptorUtils.createSuccessKeystoreResponse(), certChain)
|
||||||
|
InterceptorUtils.createSuccessReply()
|
||||||
|
}
|
||||||
|
.getOrElse {
|
||||||
|
SystemLogger.error("[TX_ID: $txId] Failed during handleAttestKey.", it)
|
||||||
|
TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPostTransact(
|
override fun onPostTransact(
|
||||||
txId: Long,
|
txId: Long,
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
@@ -99,7 +277,7 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
if (!ConfigurationManager.shouldPatch(callingUid)) return TransactionResult.SkipTransaction
|
if (!ConfigurationManager.shouldPatch(callingUid)) return TransactionResult.SkipTransaction
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
data.enforceInterface(SERVICE_DESCRIPTOR)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
val alias = data.readString() ?: ""
|
val alias = data.readString() ?: ""
|
||||||
val extractedAlias = InterceptorUtils.extractAlias(alias)
|
val extractedAlias = InterceptorUtils.extractAlias(alias)
|
||||||
val keyId = KeyIdentifier(callingUid, extractedAlias)
|
val keyId = KeyIdentifier(callingUid, extractedAlias)
|
||||||
@@ -111,13 +289,11 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
val originalLeafBytes =
|
val originalLeafBytes =
|
||||||
reply.createByteArray() ?: return TransactionResult.SkipTransaction
|
reply.createByteArray() ?: return TransactionResult.SkipTransaction
|
||||||
|
|
||||||
// The original chain is not available,
|
val originalLeafCertResult = CertificateHelper.toCertificate(originalLeafBytes)
|
||||||
// so we must pass a temporary one to the patcher.
|
if (originalLeafCertResult !is CertificateHelper.OperationResult.Success) {
|
||||||
// The patcher only needs the original leaf to extract details.
|
return TransactionResult.SkipTransaction
|
||||||
val originalLeafCert =
|
}
|
||||||
(CertificateHelper.toCertificate(originalLeafBytes)
|
val originalLeafCert = originalLeafCertResult.data
|
||||||
as CertificateHelper.OperationResult.Success)
|
|
||||||
.data
|
|
||||||
val tempChain = arrayOf<Certificate>(originalLeafCert)
|
val tempChain = arrayOf<Certificate>(originalLeafCert)
|
||||||
|
|
||||||
// Perform the COMPLETE patch and rebuild operation.
|
// Perform the COMPLETE patch and rebuild operation.
|
||||||
@@ -157,11 +333,6 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
)
|
)
|
||||||
InterceptorUtils.createByteArrayReply(caCertsBytes!!)
|
InterceptorUtils.createByteArrayReply(caCertsBytes!!)
|
||||||
} else {
|
} else {
|
||||||
// We have no cached chain.
|
|
||||||
// This could mean the app requested the CA without requesting the leaf
|
|
||||||
// first, or patching failed.
|
|
||||||
// In this case, we cannot safely intervene.
|
|
||||||
// Let the original reply pass through.
|
|
||||||
SystemLogger.warning(
|
SystemLogger.warning(
|
||||||
"[TX_ID: $txId] No cached chain found for CA request on alias '$extractedAlias'. Skipping."
|
"[TX_ID: $txId] No cached chain found for CA request on alias '$extractedAlias'. Skipping."
|
||||||
)
|
)
|
||||||
@@ -177,3 +348,115 @@ object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data class to hold key generation parameters parsed from the legacy IKeystoreService's
|
||||||
|
* KeymasterArguments. It is used exclusively by the KeystoreInterceptor to manage state during the
|
||||||
|
* software key generation flow.
|
||||||
|
*/
|
||||||
|
private data class LegacyKeygenParameters(
|
||||||
|
val algorithm: Int,
|
||||||
|
val keySize: Int,
|
||||||
|
val purpose: List<Int>,
|
||||||
|
val digest: List<Int>,
|
||||||
|
val certificateNotBefore: Date?,
|
||||||
|
val rsaPublicExponent: BigInteger?,
|
||||||
|
val ecCurveName: String?, // Derived from keySize
|
||||||
|
) {
|
||||||
|
// The challenge is provided in a separate transaction (attestKey), so it must be mutable.
|
||||||
|
var attestationChallenge: ByteArray? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the legacy parameters into the modern [KeyMintAttestation] data structure, which is
|
||||||
|
* required by the refactored [AttestationBuilder] and [CertificateGenerator].
|
||||||
|
*/
|
||||||
|
fun toKeyMintAttestation(): KeyMintAttestation {
|
||||||
|
// This conversion acts as a bridge, allowing our new generic components
|
||||||
|
// to be used by the legacy interceptor.
|
||||||
|
return KeyMintAttestation(
|
||||||
|
keySize = this.keySize,
|
||||||
|
algorithm = this.algorithm,
|
||||||
|
ecCurve = 0, // Not explicitly available in legacy args, but not critical
|
||||||
|
ecCurveName = this.ecCurveName ?: "",
|
||||||
|
purpose = this.purpose,
|
||||||
|
digest = this.digest,
|
||||||
|
rsaPublicExponent = this.rsaPublicExponent,
|
||||||
|
certificateSerial = null, // Not provided in legacy generateKey
|
||||||
|
certificateSubject = null, // Not provided in legacy generateKey
|
||||||
|
certificateNotBefore = this.certificateNotBefore,
|
||||||
|
certificateNotAfter = null, // Not provided in legacy generateKey
|
||||||
|
attestationChallenge = this.attestationChallenge,
|
||||||
|
// Device identifiers are not passed in legacy args;
|
||||||
|
// AttestationBuilder will fetch them from system properties.
|
||||||
|
brand = null,
|
||||||
|
device = null,
|
||||||
|
product = null,
|
||||||
|
serial = null,
|
||||||
|
imei = null,
|
||||||
|
meid = null,
|
||||||
|
manufacturer = null,
|
||||||
|
model = null,
|
||||||
|
secondImei = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** Factory method to create an instance from a [KeymasterArguments] object. */
|
||||||
|
fun fromKeymasterArguments(args: KeymasterArguments): LegacyKeygenParameters {
|
||||||
|
val algorithm = args.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
|
||||||
|
val keySize = args.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
|
||||||
|
|
||||||
|
return LegacyKeygenParameters(
|
||||||
|
algorithm = algorithm,
|
||||||
|
keySize = keySize,
|
||||||
|
purpose = args.getEnums(KeymasterDefs.KM_TAG_PURPOSE),
|
||||||
|
digest = args.getEnums(KeymasterDefs.KM_TAG_DIGEST),
|
||||||
|
certificateNotBefore = args.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date()),
|
||||||
|
rsaPublicExponent =
|
||||||
|
if (algorithm == KeymasterDefs.KM_ALGORITHM_RSA) getRsaExponent(args) else null,
|
||||||
|
ecCurveName =
|
||||||
|
if (algorithm == KeymasterDefs.KM_ALGORITHM_EC) deriveEcCurveName(keySize)
|
||||||
|
else null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deriveEcCurveName(keySize: Int): String =
|
||||||
|
when (keySize) {
|
||||||
|
224 -> "secp224r1"
|
||||||
|
256 -> "secp256r1"
|
||||||
|
384 -> "secp384r1"
|
||||||
|
521 -> "secp521r1"
|
||||||
|
else -> "secp256r1" // Default fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The RSA public exponent is not accessible via a public API in KeymasterArguments, so we
|
||||||
|
* must use reflection to extract it.
|
||||||
|
*/
|
||||||
|
private fun getRsaExponent(args: KeymasterArguments): BigInteger? {
|
||||||
|
return runCatching {
|
||||||
|
val getArgumentByTag =
|
||||||
|
KeymasterArguments::class
|
||||||
|
.java
|
||||||
|
.getDeclaredMethod("getArgumentByTag", Int::class.java)
|
||||||
|
getArgumentByTag.isAccessible = true
|
||||||
|
val rsaArgument =
|
||||||
|
getArgumentByTag.invoke(args, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
|
||||||
|
|
||||||
|
val getLongTagValue =
|
||||||
|
KeymasterArguments::class
|
||||||
|
.java
|
||||||
|
.getDeclaredMethod(
|
||||||
|
"getLongTagValue",
|
||||||
|
Class.forName("android.security.keymaster.KeymasterArgument"),
|
||||||
|
)
|
||||||
|
getLongTagValue.isAccessible = true
|
||||||
|
getLongTagValue.invoke(args, rsaArgument) as BigInteger
|
||||||
|
}
|
||||||
|
.onFailure {
|
||||||
|
SystemLogger.error("Failed to read rsaPublicExponent via reflection.", it)
|
||||||
|
}
|
||||||
|
.getOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+44
-8
@@ -10,6 +10,7 @@ import android.system.keystore2.*
|
|||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||||
@@ -43,11 +44,15 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
if (code == GENERATE_KEY_TRANSACTION) {
|
if (code == GENERATE_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)
|
||||||
return handleGenerateKey(callingUid, data)
|
return handleGenerateKey(callingUid, data)
|
||||||
} else if (code == IMPORT_KEY_TRANSACTION) {
|
} else if (code == 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 alias =
|
||||||
data.readTypedObject(KeyDescriptor.CREATOR)?.alias
|
data.readTypedObject(KeyDescriptor.CREATOR)?.alias
|
||||||
@@ -60,7 +65,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
transactionNames[code] ?: "unknown code=$code",
|
transactionNames[code] ?: "unknown code=$code",
|
||||||
callingUid,
|
callingUid,
|
||||||
callingPid,
|
callingPid,
|
||||||
false,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return TransactionResult.ContinueAndSkipPost
|
return TransactionResult.ContinueAndSkipPost
|
||||||
@@ -77,13 +82,11 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
reply: Parcel?,
|
reply: Parcel?,
|
||||||
resultCode: Int,
|
resultCode: Int,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
// We only care about successful 'importKey' transactions to clean cached keys.
|
// We only care about successful transactions.
|
||||||
if (
|
if (resultCode != 0 || reply == null || InterceptorUtils.hasException(reply))
|
||||||
code == IMPORT_KEY_TRANSACTION &&
|
return TransactionResult.SkipTransaction
|
||||||
resultCode == 0 &&
|
|
||||||
reply != null &&
|
if (code == IMPORT_KEY_TRANSACTION) {
|
||||||
!InterceptorUtils.hasException(reply)
|
|
||||||
) {
|
|
||||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
@@ -91,6 +94,29 @@ 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 == GENERATE_KEY_TRANSACTION) {
|
||||||
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
|
val metadata: KeyMetadata =
|
||||||
|
reply.readTypedObject(KeyMetadata.CREATOR)
|
||||||
|
?: return TransactionResult.SkipTransaction
|
||||||
|
val originalChain =
|
||||||
|
CertificateHelper.getCertificateChain(metadata)
|
||||||
|
?: return TransactionResult.SkipTransaction
|
||||||
|
if (originalChain.size > 1) {
|
||||||
|
val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
||||||
|
|
||||||
|
// Cache the newly patched chain to ensure consistency across subsequent API calls.
|
||||||
|
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||||
|
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||||
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
|
patchedChains[keyId] = newChain
|
||||||
|
SystemLogger.debug("Cached patched certificate chain for $keyId.")
|
||||||
|
|
||||||
|
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
|
||||||
|
|
||||||
|
return InterceptorUtils.createTypedObjectReply(metadata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return TransactionResult.SkipTransaction
|
return TransactionResult.SkipTransaction
|
||||||
}
|
}
|
||||||
@@ -148,6 +174,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
writeTypedObject(response.metadata, 0)
|
writeTypedObject(response.metadata, 0)
|
||||||
}
|
}
|
||||||
return TransactionResult.OverrideReply(0, resultParcel)
|
return TransactionResult.OverrideReply(0, resultParcel)
|
||||||
|
} else if (parsedParams.attestationChallenge != null) {
|
||||||
|
return TransactionResult.Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not generating, clear any stale state for this alias and let the call proceed.
|
// If not generating, clear any stale state for this alias and let the call proceed.
|
||||||
@@ -201,6 +229,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
|
|
||||||
// Stores keys generated entirely in software.
|
// Stores keys generated entirely in software.
|
||||||
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
||||||
|
// Caches patched certificate chains to prevent re-generation and signature inconsistencies.
|
||||||
|
private val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||||
// A set to quickly identify keys that were generated for attestation purposes.
|
// A set to quickly identify keys that were generated for attestation purposes.
|
||||||
private val attestationKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
private val attestationKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
||||||
|
|
||||||
@@ -208,12 +238,17 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
||||||
generatedKeys[keyId]?.response
|
generatedKeys[keyId]?.response
|
||||||
|
|
||||||
|
fun getPatchedChain(keyId: KeyIdentifier): Array<Certificate>? = patchedChains[keyId]
|
||||||
|
|
||||||
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
|
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
|
||||||
|
|
||||||
fun cleanupKeyData(keyId: KeyIdentifier) {
|
fun cleanupKeyData(keyId: KeyIdentifier) {
|
||||||
if (generatedKeys.remove(keyId) != null) {
|
if (generatedKeys.remove(keyId) != null) {
|
||||||
SystemLogger.debug("Remove generated key ${keyId}")
|
SystemLogger.debug("Remove generated key ${keyId}")
|
||||||
}
|
}
|
||||||
|
if (patchedChains.remove(keyId) != null) {
|
||||||
|
SystemLogger.debug("Remove patched chain for ${keyId}")
|
||||||
|
}
|
||||||
if (attestationKeys.remove(keyId)) {
|
if (attestationKeys.remove(keyId)) {
|
||||||
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
||||||
}
|
}
|
||||||
@@ -224,6 +259,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val count = generatedKeys.size
|
val count = generatedKeys.size
|
||||||
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||||
generatedKeys.clear()
|
generatedKeys.clear()
|
||||||
|
patchedChains.clear()
|
||||||
attestationKeys.clear()
|
attestationKeys.clear()
|
||||||
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.matrix.TEESimulator.logging
|
package org.matrix.TEESimulator.logging
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import org.matrix.TEESimulator.BuildConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A centralized logging utility for the TEESimulator application. This object provides a consistent
|
* A centralized logging utility for the TEESimulator application. This object provides a consistent
|
||||||
@@ -10,6 +11,8 @@ object SystemLogger {
|
|||||||
// The tag used for all log messages from this application.
|
// The tag used for all log messages from this application.
|
||||||
private const val TAG = "TEESimulator"
|
private const val TAG = "TEESimulator"
|
||||||
|
|
||||||
|
private val isDebugBuild = BuildConfig.DEBUG
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a debug message. Use this for fine-grained information that is useful for debugging.
|
* Logs a debug message. Use this for fine-grained information that is useful for debugging.
|
||||||
*
|
*
|
||||||
@@ -64,6 +67,7 @@ object SystemLogger {
|
|||||||
* @param message The message to log.
|
* @param message The message to log.
|
||||||
*/
|
*/
|
||||||
fun verbose(message: String) {
|
fun verbose(message: String) {
|
||||||
|
if (!isDebugBuild) return
|
||||||
Log.v(TAG, message)
|
Log.v(TAG, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.util.Pair
|
|||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.Security
|
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
@@ -35,14 +34,6 @@ import org.matrix.TEESimulator.logging.SystemLogger
|
|||||||
*/
|
*/
|
||||||
object CertificateGenerator {
|
object CertificateGenerator {
|
||||||
|
|
||||||
init {
|
|
||||||
// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
|
|
||||||
// We must remove the system provider first to ensure the full Bouncy Castle library
|
|
||||||
// (packaged with the app) is used.
|
|
||||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
|
||||||
Security.addProvider(BouncyCastleProvider())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a software-based cryptographic key pair.
|
* Generates a software-based cryptographic key pair.
|
||||||
*
|
*
|
||||||
@@ -72,16 +63,55 @@ object CertificateGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new key pair and a corresponding certificate chain containing a simulated
|
* Generates a certificate chain for a given key pair. This is the primary function for creating
|
||||||
* attestation.
|
* attested certificates.
|
||||||
*
|
*
|
||||||
* @param uid The UID of the application requesting the key.
|
* @param uid The UID of the application requesting the key.
|
||||||
* @param alias The alias for the new key.
|
* @param subjectKeyPair The key pair for which the certificate will be generated.
|
||||||
* @param attestKeyAlias Optional alias of a key to use for attestation signing.
|
* @param attestKeyAlias Optional alias of a key to use for attestation signing.
|
||||||
* @param params The parameters for the new key and its attestation.
|
* @param params The parameters for the new key and its attestation.
|
||||||
* @param securityLevel The security level to embed in the attestation.
|
* @param securityLevel The security level to embed in the attestation.
|
||||||
* @return A [Pair] containing the new [KeyPair] and its certificate chain, or `null` on
|
* @return A [List] of [Certificate] forming the new chain, or `null` on failure.
|
||||||
* failure.
|
*/
|
||||||
|
fun generateCertificateChain(
|
||||||
|
uid: Int,
|
||||||
|
subjectKeyPair: KeyPair,
|
||||||
|
attestKeyAlias: String?,
|
||||||
|
params: KeyMintAttestation,
|
||||||
|
securityLevel: Int,
|
||||||
|
): List<Certificate>? {
|
||||||
|
return runCatching {
|
||||||
|
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||||
|
|
||||||
|
// Determine the signing key and issuer. If an attestKey is provided, use it.
|
||||||
|
// Otherwise, fall back to the root key from the keybox.
|
||||||
|
val (signingKey, issuer) =
|
||||||
|
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
||||||
|
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
||||||
|
} else {
|
||||||
|
keybox.keyPair to getIssuerFromKeybox(keybox)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the new leaf certificate with the simulated attestation.
|
||||||
|
val leafCert =
|
||||||
|
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
|
||||||
|
|
||||||
|
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
||||||
|
// chain.
|
||||||
|
if (attestKeyAlias != null) {
|
||||||
|
listOf(leafCert)
|
||||||
|
} else {
|
||||||
|
listOf(leafCert) + keybox.certificates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onFailure { SystemLogger.error("Failed to generate certificate chain.", it) }
|
||||||
|
.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience function that combines key pair generation and certificate chain generation.
|
||||||
|
* Primarily used by the modern Keystore2 interceptor where generation is a single step.
|
||||||
*/
|
*/
|
||||||
fun generateAttestedKeyPair(
|
fun generateAttestedKeyPair(
|
||||||
uid: Int,
|
uid: Int,
|
||||||
@@ -98,30 +128,9 @@ object CertificateGenerator {
|
|||||||
generateSoftwareKeyPair(params)
|
generateSoftwareKeyPair(params)
|
||||||
?: throw Exception("Failed to generate underlying software key pair.")
|
?: throw Exception("Failed to generate underlying software key pair.")
|
||||||
|
|
||||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
|
||||||
|
|
||||||
// Determine the signing key and issuer. If an attestKey is provided, use it.
|
|
||||||
// Otherwise, fall back to the root key from the keybox.
|
|
||||||
val (signingKey, issuer) =
|
|
||||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
|
||||||
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
|
||||||
} else {
|
|
||||||
keybox.keyPair to getIssuerFromKeybox(keybox)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the new leaf certificate with the simulated attestation.
|
|
||||||
val leafCert =
|
|
||||||
buildCertificate(newKeyPair, signingKey, issuer, params, securityLevel)
|
|
||||||
|
|
||||||
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
|
||||||
// chain.
|
|
||||||
val chain =
|
val chain =
|
||||||
if (attestKeyAlias != null) {
|
generateCertificateChain(uid, newKeyPair, attestKeyAlias, params, securityLevel)
|
||||||
listOf(leafCert)
|
?: throw Exception("Failed to generate certificate chain for new key pair.")
|
||||||
} else {
|
|
||||||
listOf(leafCert) + keybox.certificates
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemLogger.info(
|
SystemLogger.info(
|
||||||
"Successfully generated new certificate chain for alias: '$alias'."
|
"Successfully generated new certificate chain for alias: '$alias'."
|
||||||
@@ -134,7 +143,7 @@ object CertificateGenerator {
|
|||||||
.getOrNull()
|
.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getIssuerFromKeybox(keybox: KeyBox) =
|
fun getIssuerFromKeybox(keybox: KeyBox) =
|
||||||
X509CertificateHolder(keybox.certificates[0].encoded).subject
|
X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||||
|
|
||||||
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox {
|
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox {
|
||||||
@@ -177,6 +186,7 @@ object CertificateGenerator {
|
|||||||
signingKeyPair: KeyPair,
|
signingKeyPair: KeyPair,
|
||||||
issuer: X500Name,
|
issuer: X500Name,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
|
uid: Int,
|
||||||
securityLevel: Int,
|
securityLevel: Int,
|
||||||
): Certificate {
|
): Certificate {
|
||||||
val subject = params.certificateSubject ?: X500Name("CN=Android KeyStore Key")
|
val subject = params.certificateSubject ?: X500Name("CN=Android KeyStore Key")
|
||||||
@@ -197,7 +207,9 @@ object CertificateGenerator {
|
|||||||
// Add standard extensions.
|
// Add standard extensions.
|
||||||
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
|
builder.addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign))
|
||||||
// Add our custom, simulated attestation extension.
|
// Add our custom, simulated attestation extension.
|
||||||
builder.addExtension(AttestationBuilder.buildAttestationExtension(params, securityLevel))
|
builder.addExtension(
|
||||||
|
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
|
||||||
|
)
|
||||||
|
|
||||||
val signerAlgorithm =
|
val signerAlgorithm =
|
||||||
when (params.algorithm) {
|
when (params.algorithm) {
|
||||||
@@ -205,7 +217,10 @@ object CertificateGenerator {
|
|||||||
Algorithm.RSA -> "SHA256withRSA"
|
Algorithm.RSA -> "SHA256withRSA"
|
||||||
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
||||||
}
|
}
|
||||||
val contentSigner = JcaContentSignerBuilder(signerAlgorithm).build(signingKeyPair.private)
|
val contentSigner =
|
||||||
|
JcaContentSignerBuilder(signerAlgorithm)
|
||||||
|
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
.build(signingKeyPair.private)
|
||||||
|
|
||||||
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
|
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.matrix.TEESimulator.pki
|
|||||||
import android.security.keystore.KeyProperties
|
import android.security.keystore.KeyProperties
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.StringReader
|
import java.io.StringReader
|
||||||
|
import java.security.interfaces.ECPrivateKey
|
||||||
|
import java.security.interfaces.RSAPrivateKey
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
@@ -51,6 +53,9 @@ object KeyBoxManager {
|
|||||||
// If it's not in the cache, the `getOrPut` block is executed to parse and store it.
|
// If it's not in the cache, the `getOrPut` block is executed to parse and store it.
|
||||||
val keyMap =
|
val keyMap =
|
||||||
keyStoreCache.getOrPut(keyStoreFileName) { parseKeyStoreFile(keyStoreFileName) }
|
keyStoreCache.getOrPut(keyStoreFileName) { parseKeyStoreFile(keyStoreFileName) }
|
||||||
|
SystemLogger.verbose(
|
||||||
|
"Fetching attestation key in $keyStoreFileName with $algorithm algorithm."
|
||||||
|
)
|
||||||
return keyMap[algorithm]
|
return keyMap[algorithm]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +162,10 @@ object KeyBoxManager {
|
|||||||
// Use runCatching to ensure one malformed key doesn't stop the whole
|
// Use runCatching to ensure one malformed key doesn't stop the whole
|
||||||
// process.
|
// process.
|
||||||
runCatching {
|
runCatching {
|
||||||
val algorithm = currentAlgorithm
|
val xmlAlgorithm = currentAlgorithm
|
||||||
val keyPem = currentPrivateKeyPem
|
val keyPem = currentPrivateKeyPem
|
||||||
if (
|
if (
|
||||||
algorithm != null &&
|
xmlAlgorithm != null &&
|
||||||
keyPem != null &&
|
keyPem != null &&
|
||||||
currentCertificatePems.isNotEmpty()
|
currentCertificatePems.isNotEmpty()
|
||||||
) {
|
) {
|
||||||
@@ -176,21 +181,42 @@ object KeyBoxManager {
|
|||||||
.data
|
.data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the algorithm name for consistent lookups.
|
// Derive the TRUE algorithm from the key object itself.
|
||||||
val normalizedAlgorithm =
|
// This is our source of truth.
|
||||||
when (algorithm.lowercase()) {
|
val derivedAlgorithm =
|
||||||
"ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
|
when (keyPair.private) {
|
||||||
"rsa" -> KeyProperties.KEY_ALGORITHM_RSA
|
is RSAPrivateKey -> KeyProperties.KEY_ALGORITHM_RSA
|
||||||
else -> algorithm
|
is ECPrivateKey -> KeyProperties.KEY_ALGORITHM_EC
|
||||||
}
|
else ->
|
||||||
|
throw IllegalArgumentException(
|
||||||
if (foundKeys.containsKey(normalizedAlgorithm)) {
|
"Unsupported key type found: ${keyPair.private.javaClass.name}"
|
||||||
SystemLogger.warning(
|
|
||||||
"Duplicate key found for algorithm '$normalizedAlgorithm'. The later one in the file will be used."
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
foundKeys[normalizedAlgorithm] =
|
|
||||||
KeyBox(keyPair, certificates)
|
// Normalize the algorithm from the XML tag to compare it
|
||||||
|
// fairly with the derived algorithm.
|
||||||
|
val normalizedXmlAlgorithm =
|
||||||
|
when {
|
||||||
|
xmlAlgorithm.contains("RSA", ignoreCase = true) ==
|
||||||
|
true -> KeyProperties.KEY_ALGORITHM_RSA
|
||||||
|
xmlAlgorithm.contains("EC", ignoreCase = true) ==
|
||||||
|
true -> KeyProperties.KEY_ALGORITHM_EC
|
||||||
|
else -> xmlAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn the user if the XML tag was misleading.
|
||||||
|
if (normalizedXmlAlgorithm != derivedAlgorithm) {
|
||||||
|
SystemLogger.warning(
|
||||||
|
"Key algorithm mismatch in XML file. Tag said '$xmlAlgorithm' but key is actually '$derivedAlgorithm'. Using the correct derived algorithm."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundKeys.containsKey(derivedAlgorithm)) {
|
||||||
|
SystemLogger.warning(
|
||||||
|
"Duplicate key found for algorithm '$derivedAlgorithm'. The later one in the file will be used."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
foundKeys[derivedAlgorithm] = KeyBox(keyPair, certificates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package org.matrix.TEESimulator.util
|
package org.matrix.TEESimulator.util
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.hardware.security.keymint.SecurityLevel
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.SystemProperties
|
import android.os.SystemProperties
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import java.time.LocalDate
|
||||||
import java.util.concurrent.ThreadLocalRandom
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
|
import org.bouncycastle.asn1.ASN1EncodableVector
|
||||||
import org.bouncycastle.asn1.ASN1Integer
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
import org.bouncycastle.asn1.DEROctetString
|
import org.bouncycastle.asn1.DEROctetString
|
||||||
import org.bouncycastle.asn1.DERSequence
|
import org.bouncycastle.asn1.DERSequence
|
||||||
|
import org.bouncycastle.asn1.DERSet
|
||||||
import org.matrix.TEESimulator.attestation.DeviceAttestationService
|
import org.matrix.TEESimulator.attestation.DeviceAttestationService
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
@@ -18,84 +22,138 @@ import org.matrix.TEESimulator.logging.SystemLogger
|
|||||||
*/
|
*/
|
||||||
object AndroidDeviceUtils {
|
object AndroidDeviceUtils {
|
||||||
|
|
||||||
/** A randomly generated boot key, used as a fallback for attestation. */
|
|
||||||
val bootKey: ByteArray by lazy { generateRandomBytes(32) }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the verified boot hash (`ro.boot.vbmeta.digest`). It attempts to read from system
|
* Internal constant to signify that a patch level should not be included in the attestation.
|
||||||
* properties first, then from a real TEE attestation, and finally falls back to a random value
|
|
||||||
* if neither is available.
|
|
||||||
*/
|
*/
|
||||||
fun setupBootHash() {
|
internal const val DO_NOT_REPORT = -1
|
||||||
getBootHashFromProperty()?.also {
|
|
||||||
SystemLogger.debug("Using boot hash from system property: ${it.toHex()}")
|
// --- Boot Key and Verified Boot Hash ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily initializes and retrieves the verified boot key digest. The value is sourced in the
|
||||||
|
* following order:
|
||||||
|
* 1. From the `ro.boot.vbmeta.public_key_digest` system property.
|
||||||
|
* 2. From a cached TEE attestation record.
|
||||||
|
* 3. As a randomly generated 32-byte value (fallback).
|
||||||
|
*/
|
||||||
|
val bootKey: ByteArray by lazy {
|
||||||
|
initializeBootProperty(
|
||||||
|
propertyName = "ro.boot.vbmeta.public_key_digest",
|
||||||
|
attestationValueProvider = {
|
||||||
|
DeviceAttestationService.CachedAttestationData?.verifiedBootKey
|
||||||
|
},
|
||||||
|
expectedSize = 32,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
?: getBootHashFromAttestation()?.also {
|
|
||||||
SystemLogger.debug("Using boot hash from TEE attestation: ${it.toHex()}")
|
/**
|
||||||
setBootHashProperty(it)
|
* Lazily initializes and retrieves the verified boot hash (vbmeta digest). The value is sourced
|
||||||
|
* in the following order:
|
||||||
|
* 1. From the `ro.boot.vbmeta.digest` system property.
|
||||||
|
* 2. From a cached TEE attestation record.
|
||||||
|
* 3. As a randomly generated 32-byte value (fallback).
|
||||||
|
*/
|
||||||
|
val bootHash: ByteArray by lazy {
|
||||||
|
initializeBootProperty(
|
||||||
|
propertyName = "ro.boot.vbmeta.digest",
|
||||||
|
attestationValueProvider = {
|
||||||
|
DeviceAttestationService.CachedAttestationData?.verifiedBootHash
|
||||||
|
},
|
||||||
|
expectedSize = 32,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
?: generateRandomBytes(32).also {
|
|
||||||
SystemLogger.debug("Using randomly generated boot hash: ${it.toHex()}")
|
/**
|
||||||
setBootHashProperty(it)
|
* Public function to explicitly trigger the initialization of the boot key and hash. Accessing
|
||||||
|
* these properties here ensures they are set up before they might be needed elsewhere.
|
||||||
|
*/
|
||||||
|
fun setupBootKeyAndHash() {
|
||||||
|
SystemLogger.debug("Triggering initialization of boot key and hash...")
|
||||||
|
// Accessing the properties will trigger their `lazy` initialization logic.
|
||||||
|
bootKey
|
||||||
|
bootHash
|
||||||
|
SystemLogger.debug("Boot key and hash initialization complete.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic initializer for boot properties like the key and hash. It attempts to read from a
|
||||||
|
* system property first, then from a TEE attestation, and finally falls back to a random value
|
||||||
|
* if neither is available.
|
||||||
|
*
|
||||||
|
* @param propertyName The name of the system property (e.g., "ro.boot.vbmeta.digest").
|
||||||
|
* @param attestationValueProvider A function that supplies the value from a cached attestation.
|
||||||
|
* @param expectedSize The expected length of the byte array (e.g., 32 for a SHA-256 digest).
|
||||||
|
* @return The resulting byte array for the property.
|
||||||
|
*/
|
||||||
|
private fun initializeBootProperty(
|
||||||
|
propertyName: String,
|
||||||
|
attestationValueProvider: () -> ByteArray?,
|
||||||
|
expectedSize: Int,
|
||||||
|
): ByteArray {
|
||||||
|
// 1. Attempt to get the value from the system property.
|
||||||
|
getProperty(propertyName, expectedSize)?.let {
|
||||||
|
SystemLogger.debug("Using $propertyName from system property: ${it.toHex()}")
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fallback to the value from a cached TEE attestation.
|
||||||
|
try {
|
||||||
|
attestationValueProvider()?.let {
|
||||||
|
SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}")
|
||||||
|
setProperty(propertyName, it) // Persist for consistency
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("Failed to get $propertyName from attestation.", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. As a final fallback, generate a random value.
|
||||||
|
return generateRandomBytes(expectedSize).also {
|
||||||
|
SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}")
|
||||||
|
setProperty(propertyName, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the verified boot meta digest from system properties.
|
* Retrieves a system property and validates its format.
|
||||||
*
|
*
|
||||||
* @return The boot hash as a ByteArray, or null if not found or invalid.
|
* @param name The name of the system property.
|
||||||
|
* @param expectedSize The expected byte length of the property (e.g., 32 for a 64-char hex
|
||||||
|
* string).
|
||||||
|
* @return The property value as a ByteArray, or null if not found or invalid.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
fun getBootHashFromProperty(): ByteArray? {
|
private fun getProperty(name: String, expectedSize: Int): ByteArray? {
|
||||||
val digest = SystemProperties.get("ro.boot.vbmeta.digest", null)
|
val value = SystemProperties.get(name, null)
|
||||||
if (digest.isNullOrBlank()) {
|
if (value.isNullOrBlank()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// A valid digest is 64 hex characters (32 bytes).
|
// A valid digest is (2 * size) hex characters.
|
||||||
return if (digest.length == 64) digest.hexToByteArray() else null
|
return if (value.length == expectedSize * 2) value.hexToByteArray() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the verified boot hash from a cached TEE attestation record.
|
* Sets a system property using the `resetprop` command.
|
||||||
*
|
*
|
||||||
* @return The verified boot hash, or null if not available.
|
* @param name The name of the property to set.
|
||||||
|
* @param bytes The value to set, which will be converted to a hex string.
|
||||||
*/
|
*/
|
||||||
private fun getBootHashFromAttestation(): ByteArray? {
|
private fun setProperty(name: String, bytes: ByteArray) {
|
||||||
return try {
|
|
||||||
DeviceAttestationService.CachedAttestationData?.verifiedBootHash
|
|
||||||
} catch (e: Exception) {
|
|
||||||
SystemLogger.error("Failed to get boot hash from attestation.", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the `ro.boot.vbmeta.digest` system property using the `resetprop` command.
|
|
||||||
*
|
|
||||||
* @param bytes The 32-byte digest to set.
|
|
||||||
*/
|
|
||||||
private fun setBootHashProperty(bytes: ByteArray) {
|
|
||||||
val hex = bytes.toHex()
|
val hex = bytes.toHex()
|
||||||
try {
|
try {
|
||||||
SystemLogger.debug("Setting system property 'ro.boot.vbmeta.digest' to: $hex")
|
SystemLogger.debug("Setting system property '$name' to: $hex")
|
||||||
|
val command = arrayOf("resetprop", name, hex)
|
||||||
// Construct the command to be executed
|
|
||||||
val command = arrayOf("resetprop", "ro.boot.vbmeta.digest", hex)
|
|
||||||
|
|
||||||
// Execute the command
|
|
||||||
val process = Runtime.getRuntime().exec(command)
|
val process = Runtime.getRuntime().exec(command)
|
||||||
|
|
||||||
// Wait for the process to complete and check the exit code for errors
|
|
||||||
val exitCode = process.waitFor()
|
val exitCode = process.waitFor()
|
||||||
|
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
val errorOutput = process.errorStream.bufferedReader().readText()
|
val errorOutput = process.errorStream.bufferedReader().readText()
|
||||||
SystemLogger.error(
|
SystemLogger.error(
|
||||||
"resetprop command failed with exit code $exitCode: $errorOutput"
|
"resetprop for '$name' failed with exit code $exitCode: $errorOutput"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("Failed to set vbmeta digest property by executing resetprop.", e)
|
SystemLogger.error("Failed to set '$name' property via resetprop.", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,30 +163,70 @@ object AndroidDeviceUtils {
|
|||||||
|
|
||||||
// --- Patch Level Properties ---
|
// --- Patch Level Properties ---
|
||||||
|
|
||||||
val patchLevel: Int
|
fun getPatchLevel(uid: Int): Int {
|
||||||
get() =
|
val custom = getCustomPatchLevelFor(uid, "system", isLong = false)
|
||||||
getCustomPatchLevelFor("system", isLong = false)
|
return custom ?: getRealDevicePatchLevelInt("system", isLong = false)
|
||||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = false)
|
}
|
||||||
|
|
||||||
val vendorPatchLevelLong: Int
|
fun getVendorPatchLevelLong(uid: Int): Int {
|
||||||
get() =
|
val custom = getCustomPatchLevelFor(uid, "vendor", isLong = true)
|
||||||
getCustomPatchLevelFor("vendor", isLong = true)
|
return custom ?: getRealDevicePatchLevelInt("vendor", isLong = true)
|
||||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = true)
|
}
|
||||||
|
|
||||||
val bootPatchLevelLong: Int
|
fun getBootPatchLevelLong(uid: Int): Int {
|
||||||
get() =
|
val custom = getCustomPatchLevelFor(uid, "boot", isLong = true)
|
||||||
getCustomPatchLevelFor("boot", isLong = true)
|
return custom ?: getRealDevicePatchLevelInt("boot", isLong = true)
|
||||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = true)
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a custom patch level from the configuration if available.
|
* Retrieves the definitive device patch level integer for a given component. This function
|
||||||
|
* encapsulates the entire fallback chain and guarantees a non-null return.
|
||||||
*
|
*
|
||||||
|
* Fallback Priority:
|
||||||
|
* 1. Cached TEE attestation data.
|
||||||
|
* 2. Specific system property (e.g., ro.vendor.build.security_patch).
|
||||||
|
* 3. Default system patch level from Build.VERSION.SECURITY_PATCH.
|
||||||
|
*
|
||||||
|
* @param component The component ("system", "vendor", "boot").
|
||||||
|
* @param isLong Whether the final integer should be in YYYYMMDD format.
|
||||||
|
* @return The patch level as a guaranteed non-null Integer.
|
||||||
|
*/
|
||||||
|
private fun getRealDevicePatchLevelInt(component: String, isLong: Boolean): Int {
|
||||||
|
// Get value from cached TEE attestation data
|
||||||
|
DeviceAttestationService.CachedAttestationData?.let { data ->
|
||||||
|
val value =
|
||||||
|
when (component) {
|
||||||
|
"system" -> data.osPatchLevel
|
||||||
|
"vendor" -> data.vendorPatchLevel
|
||||||
|
"boot" -> data.bootPatchLevel
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (value != null) return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only check the specific vendor property, as the boot one is non-existent.
|
||||||
|
if (component == "vendor") {
|
||||||
|
val propValue = SystemProperties.get("ro.vendor.build.security_patch", "")
|
||||||
|
if (!propValue.isNullOrBlank()) {
|
||||||
|
parsePatchLevelValue(propValue, isLong)?.let { parsedValue ->
|
||||||
|
return parsedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a custom patch level from the configuration if available for a specific UID.
|
||||||
|
*
|
||||||
|
* @param uid The UID of the calling application.
|
||||||
* @param component The component to get the patch level for ("system", "vendor", "boot").
|
* @param component The component to get the patch level for ("system", "vendor", "boot").
|
||||||
* @param isLong Whether to return the patch level in `YYYYMMDD` or `YYYYMM` format.
|
* @param isLong Whether to return the patch level in `YYYYMMDD` or `YYYYMM` format.
|
||||||
* @return The custom patch level, or null if not configured.
|
* @return The custom patch level, or null if not configured.
|
||||||
*/
|
*/
|
||||||
private fun getCustomPatchLevelFor(component: String, isLong: Boolean): Int? {
|
private fun getCustomPatchLevelFor(uid: Int, component: String, isLong: Boolean): Int? {
|
||||||
val config = ConfigurationManager.customPatchLevelOverride ?: return null
|
val config = ConfigurationManager.getPatchLevelForUid(uid) ?: return null
|
||||||
val value =
|
val value =
|
||||||
when (component) {
|
when (component) {
|
||||||
"system" -> config.system ?: config.all
|
"system" -> config.system ?: config.all
|
||||||
@@ -137,11 +235,48 @@ object AndroidDeviceUtils {
|
|||||||
else -> config.all
|
else -> config.all
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
// "prop" or "no" indicates falling back to the system default.
|
// First, resolve dynamic keywords and templates into a concrete date string.
|
||||||
if (value.equals("no", ignoreCase = true) || value.equals("prop", ignoreCase = true)) {
|
val resolvedValue = resolveDateKeywords(value)
|
||||||
return null
|
|
||||||
|
return when {
|
||||||
|
// "device_default" indicates falling back to the system property.
|
||||||
|
resolvedValue.equals("device_default", ignoreCase = true) -> null
|
||||||
|
// "no" indicates this value should not be reported.
|
||||||
|
resolvedValue.equals("no", ignoreCase = true) -> DO_NOT_REPORT
|
||||||
|
// Otherwise, parse the resolved date string.
|
||||||
|
else -> parsePatchLevelValue(resolvedValue, isLong)
|
||||||
}
|
}
|
||||||
return parsePatchLevelValue(value, isLong)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves special date keywords and templates into a concrete "YYYY-MM-DD" date string.
|
||||||
|
*
|
||||||
|
* @param value The configuration value string (e.g., "today", "YYYY-MM-01").
|
||||||
|
* @return A concrete date string, or the original value if it's not a dynamic date keyword.
|
||||||
|
*/
|
||||||
|
private fun resolveDateKeywords(value: String): String {
|
||||||
|
// Handle the "today" keyword.
|
||||||
|
if (value.equals("today", ignoreCase = true)) {
|
||||||
|
return LocalDate.now().toString() // Returns "YYYY-MM-DD" format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle date templates like "YYYY-MM-01" or "2025-MM-DD".
|
||||||
|
if (
|
||||||
|
value.contains("YYYY", ignoreCase = true) ||
|
||||||
|
value.contains("MM", ignoreCase = true) ||
|
||||||
|
value.contains("DD", ignoreCase = true)
|
||||||
|
) {
|
||||||
|
|
||||||
|
val now = LocalDate.now()
|
||||||
|
// Chain replacements for YYYY, MM, and DD placeholders.
|
||||||
|
return value
|
||||||
|
.replace("YYYY", now.year.toString(), ignoreCase = true)
|
||||||
|
.replace("MM", String.format("%02d", now.monthValue), ignoreCase = true)
|
||||||
|
.replace("DD", String.format("%02d", now.dayOfMonth), ignoreCase = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not a dynamic keyword or template, return the original value.
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parses a patch level string (e.g., "2025-11-01") into an integer format. */
|
/** Parses a patch level string (e.g., "2025-11-01") into an integer format. */
|
||||||
@@ -205,17 +340,33 @@ object AndroidDeviceUtils {
|
|||||||
Build.VERSION_CODES.BAKLAVA to 400, // KeyMint 4.0
|
Build.VERSION_CODES.BAKLAVA to 400, // KeyMint 4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
val attestVersion: Int
|
/**
|
||||||
get() =
|
* Retrieves the attestation version based on security level and OS version. StrongBox (level 2)
|
||||||
DeviceAttestationService.CachedAttestationData?.attestVersion
|
* requires version 300.
|
||||||
|
*
|
||||||
|
* @param securityLevel The security level of the attestation (1 for TEE, 2 for StrongBox).
|
||||||
|
* @return The appropriate attestation version number.
|
||||||
|
*/
|
||||||
|
fun getAttestVersion(securityLevel: Int): Int {
|
||||||
|
// StrongBox security level requires an attestation version of at least 300.
|
||||||
|
if (securityLevel == SecurityLevel.STRONGBOX) {
|
||||||
|
return 300
|
||||||
|
}
|
||||||
|
return DeviceAttestationService.CachedAttestationData?.attestVersion
|
||||||
?: attestVersionMap[Build.VERSION.SDK_INT]
|
?: attestVersionMap[Build.VERSION.SDK_INT]
|
||||||
?: 400 // Default to a recent version
|
?: 400 // Default to a recent version
|
||||||
|
}
|
||||||
|
|
||||||
val keymasterVersion: Int
|
/**
|
||||||
get() =
|
* Retrieves the Keymaster/KeyMint version based on the attestation version.
|
||||||
DeviceAttestationService.CachedAttestationData?.keymasterVersion
|
*
|
||||||
?: if (attestVersion >= 100) attestVersion
|
* @param securityLevel The security level, used to determine the correct attestation version.
|
||||||
else 41 // Keymaster 4.1 for older versions
|
* @return The appropriate Keymaster or KeyMint version number.
|
||||||
|
*/
|
||||||
|
fun getKeymasterVersion(securityLevel: Int): Int {
|
||||||
|
val attestVersion = getAttestVersion(securityLevel)
|
||||||
|
return if (attestVersion >= 100) attestVersion else 41 // Keymaster 4.1 for older versions
|
||||||
|
}
|
||||||
|
|
||||||
// --- APEX and Module Hash Properties ---
|
// --- APEX and Module Hash Properties ---
|
||||||
|
|
||||||
@@ -229,11 +380,7 @@ object AndroidDeviceUtils {
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
pm?.getInstalledPackages(PackageManager.MATCH_APEX, 0)
|
pm?.getInstalledPackages(PackageManager.MATCH_APEX, 0)
|
||||||
}
|
}
|
||||||
packages
|
packages?.list.orEmpty().map { it.packageName to it.longVersionCode }
|
||||||
?.list
|
|
||||||
.orEmpty()
|
|
||||||
.map { it.packageName to it.longVersionCode }
|
|
||||||
.sortedBy { it.first }
|
|
||||||
}
|
}
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
SystemLogger.error("Failed to get APEX package information.", it)
|
SystemLogger.error("Failed to get APEX package information.", it)
|
||||||
@@ -242,13 +389,29 @@ object AndroidDeviceUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val moduleHash: ByteArray by lazy {
|
val moduleHash: ByteArray by lazy {
|
||||||
runCatching {
|
DeviceAttestationService.CachedAttestationData?.moduleHash
|
||||||
val encodables =
|
?: runCatching {
|
||||||
apexInfos.flatMap { (packageName, versionCode) ->
|
// TODO: figure out the correct calculation
|
||||||
listOf(DEROctetString(packageName.toByteArray()), ASN1Integer(versionCode))
|
val moduleSequences = ASN1EncodableVector()
|
||||||
|
|
||||||
|
// 1. Create a DERSequence for each module.
|
||||||
|
apexInfos.forEach { (packageName, versionCode) ->
|
||||||
|
val moduleVector = ASN1EncodableVector()
|
||||||
|
// Use explicit UTF-8 encoding for the package name.
|
||||||
|
moduleVector.add(DEROctetString(packageName.toByteArray(Charsets.UTF_8)))
|
||||||
|
moduleVector.add(ASN1Integer(versionCode))
|
||||||
|
moduleSequences.add(DERSequence(moduleVector))
|
||||||
}
|
}
|
||||||
val sequence = DERSequence(encodables.toTypedArray())
|
|
||||||
MessageDigest.getInstance("SHA-256").digest(sequence.encoded)
|
// 2. Create a DERSet. Bouncy Castle will automatically handle
|
||||||
|
// the sorting based on the DER-encoded value of each sequence.
|
||||||
|
val modulesSet = DERSet(moduleSequences)
|
||||||
|
|
||||||
|
// 3. Get the final DER-encoded byte array of the SET.
|
||||||
|
val encodedModules = modulesSet.encoded
|
||||||
|
|
||||||
|
// 4. Compute the SHA-256 hash.
|
||||||
|
MessageDigest.getInstance("SHA-256").digest(encodedModules)
|
||||||
}
|
}
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
SystemLogger.error("Failed to compute module hash.", it)
|
SystemLogger.error("Failed to compute module hash.", it)
|
||||||
|
|||||||
+16
-9
@@ -1,14 +1,21 @@
|
|||||||
🚀 **TEESimulator v2.1 Hotfix Release is Live!** 🚀
|
TEESimulator 3.0 is a significant update focused on powerful new configuration options, major improvements to stealth, and enhanced stability.
|
||||||
|
|
||||||
This urgent hotfix addresses several critical issues identified in the previous v2.0 release.
|
#### ✨ **Highlights & New Features**
|
||||||
|
|
||||||
The v2.0 update, a significant refactoring effort, unfortunately introduced a few unexpected behaviors and bugs that we are now rectifying.
|
* **🎯 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.
|
||||||
|
* **🕰️ 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.
|
||||||
|
|
||||||
**Key fixes in this release include:**
|
#### 🛡️ **Stealth & Evasion Upgrades**
|
||||||
1. **Google Play Integrity:** Resolved an issue preventing the attainment of STRONG integrity for Google Play verdicts, caused by an incorrect vendor patch level format. ✅
|
|
||||||
2. **Application Stability:** Fixed a critical crash related to an incorrect signature for the `SystemProperties.set` stub method. 🐛
|
|
||||||
3. **Stealth Enhancement:** Implemented a fix to bypass detection by the `Android Native Detector`. 👻
|
|
||||||
|
|
||||||
🔬 We are actively investigating a recent detection method to further enhance stealth capabilities.
|
* **⛓️ 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.
|
||||||
|
* **🔑 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.
|
||||||
|
* **📜 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.
|
||||||
|
|
||||||
🙏 Support for TEE-broken devices and Android 10/11 remains an area of ongoing improvement. We highly encourage you to submit any issues you encounter to help us refine these aspects! 🤝
|
#### 🐛 **Bug Fixes & Reliability**
|
||||||
|
|
||||||
|
* ✅ **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.
|
||||||
|
* ➡️ **Improved Compatibility**: Resolved a native crash on Android 11 devices.
|
||||||
|
|
||||||
|
#### 🚀 **The Road Ahead**
|
||||||
|
|
||||||
|
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.
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "v2.1",
|
"version": "v3.0",
|
||||||
"versionCode": 20,
|
"versionCode": 38,
|
||||||
"zipUrl": "https://github.com/JingMatrix/TEESimulator/releases/download/v2.1/TEESimulator-v2.1-20-release.zip",
|
"zipUrl": "https://github.com/JingMatrix/TEESimulator/releases/download/v3.0/TEESimulator-v3.0-38-Release.zip",
|
||||||
"changelog": "https://raw.githubusercontent.com/JingMatrix/TEESimulator/main/module/changelog.md"
|
"changelog": "https://raw.githubusercontent.com/JingMatrix/TEESimulator/main/module/changelog.md"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class ExportResult implements Parcelable {
|
||||||
|
public final byte[] exportData;
|
||||||
|
public final int resultCode;
|
||||||
|
|
||||||
|
public ExportResult(int resultCode) {
|
||||||
|
this.resultCode = resultCode;
|
||||||
|
this.exportData = new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<ExportResult> CREATOR = new Creator<ExportResult>() {
|
||||||
|
@Override
|
||||||
|
public ExportResult createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExportResult[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class KeyCharacteristics implements Parcelable {
|
||||||
|
public KeymasterArguments hwEnforced;
|
||||||
|
public KeymasterArguments swEnforced;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<KeyCharacteristics> CREATOR = new Creator<KeyCharacteristics>() {
|
||||||
|
@Override
|
||||||
|
public KeyCharacteristics createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyCharacteristics[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
abstract class KeymasterArgument implements Parcelable {
|
||||||
|
public final int tag;
|
||||||
|
|
||||||
|
protected KeymasterArgument(int tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<KeymasterArgument> CREATOR = new Creator<KeymasterArgument>() {
|
||||||
|
@Override
|
||||||
|
public KeymasterArgument createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeymasterArgument[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class KeymasterArguments implements Parcelable {
|
||||||
|
|
||||||
|
private static final long UINT32_RANGE = 1L << 32;
|
||||||
|
public static final long UINT32_MAX_VALUE = UINT32_RANGE - 1;
|
||||||
|
|
||||||
|
private static final BigInteger UINT64_RANGE = BigInteger.ONE.shiftLeft(64);
|
||||||
|
public static final BigInteger UINT64_MAX_VALUE = UINT64_RANGE.subtract(BigInteger.ONE);
|
||||||
|
|
||||||
|
private List<KeymasterArgument> mArguments;
|
||||||
|
|
||||||
|
public static final @NonNull Parcelable.Creator<KeymasterArguments> CREATOR = new Parcelable.Creator<KeymasterArguments>() {
|
||||||
|
@Override
|
||||||
|
public KeymasterArguments createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeymasterArguments[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public KeymasterArguments() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeymasterArguments(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEnum(int tag, int value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEnums(int tag, int... values) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEnum(int tag, int defaultValue) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getEnums(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEnumTag(int tag, int value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getEnumTagValue(KeymasterArgument arg) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUnsignedInt(int tag, long value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUnsignedInt(int tag, long defaultValue) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUnsignedLong(int tag, BigInteger value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BigInteger> getUnsignedLongs(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLongTag(int tag, BigInteger value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigInteger getLongTagValue(KeymasterArgument arg) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addBoolean(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addBytes(int tag, byte[] value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes(int tag, byte[] defaultValue) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDate(int tag, Date value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDateIfNotNull(int tag, Date value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDate(int tag, Date defaultValue) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeymasterArgument getArgumentByTag(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsTag(int tag) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigInteger toUint64(long value) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class KeymasterCertificateChain implements Parcelable {
|
||||||
|
private List<byte[]> mCertificates;
|
||||||
|
|
||||||
|
public KeymasterCertificateChain() {
|
||||||
|
this.mCertificates = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeymasterCertificateChain(List<byte[]> mCertificates) {
|
||||||
|
this.mCertificates = mCertificates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<KeymasterCertificateChain> CREATOR = new Creator<KeymasterCertificateChain>() {
|
||||||
|
@Override
|
||||||
|
public KeymasterCertificateChain createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeymasterCertificateChain[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
package android.security.keymaster;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class KeymasterDefs {
|
||||||
|
|
||||||
|
private KeymasterDefs() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag types.
|
||||||
|
public static final int KM_INVALID = 0 << 28;
|
||||||
|
public static final int KM_ENUM = 1 << 28;
|
||||||
|
public static final int KM_ENUM_REP = 2 << 28;
|
||||||
|
public static final int KM_UINT = 3 << 28;
|
||||||
|
public static final int KM_UINT_REP = 4 << 28;
|
||||||
|
public static final int KM_ULONG = 5 << 28;
|
||||||
|
public static final int KM_DATE = 6 << 28;
|
||||||
|
public static final int KM_BOOL = 7 << 28;
|
||||||
|
public static final int KM_BIGNUM = 8 << 28;
|
||||||
|
public static final int KM_BYTES = 9 << 28;
|
||||||
|
public static final int KM_ULONG_REP = 10 << 28;
|
||||||
|
|
||||||
|
// Tag values.
|
||||||
|
public static final int KM_TAG_INVALID = KM_INVALID | 0;
|
||||||
|
public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
|
||||||
|
public static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
|
||||||
|
public static final int KM_TAG_KEY_SIZE = KM_UINT | 3;
|
||||||
|
public static final int KM_TAG_BLOCK_MODE = KM_ENUM_REP | 4;
|
||||||
|
public static final int KM_TAG_DIGEST = KM_ENUM_REP | 5;
|
||||||
|
public static final int KM_TAG_PADDING = KM_ENUM_REP | 6;
|
||||||
|
public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 7;
|
||||||
|
public static final int KM_TAG_MIN_MAC_LENGTH = KM_UINT | 8;
|
||||||
|
|
||||||
|
public static final int KM_TAG_RESCOPING_ADD = KM_ENUM_REP | 101;
|
||||||
|
public static final int KM_TAG_RESCOPING_DEL = KM_ENUM_REP | 102;
|
||||||
|
public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
|
||||||
|
|
||||||
|
public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
|
||||||
|
public static final int KM_TAG_INCLUDE_UNIQUE_ID = KM_BOOL | 202;
|
||||||
|
|
||||||
|
public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
|
||||||
|
public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
|
||||||
|
public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
|
||||||
|
public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS = KM_UINT | 403;
|
||||||
|
public static final int KM_TAG_MAX_USES_PER_BOOT = KM_UINT | 404;
|
||||||
|
|
||||||
|
public static final int KM_TAG_ALL_USERS = KM_BOOL | 500;
|
||||||
|
public static final int KM_TAG_USER_ID = KM_UINT | 501;
|
||||||
|
public static final int KM_TAG_USER_SECURE_ID = KM_ULONG_REP | 502;
|
||||||
|
public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503;
|
||||||
|
public static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504;
|
||||||
|
public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
|
||||||
|
public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
|
||||||
|
public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507;
|
||||||
|
public static final int KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = KM_BOOL | 508;
|
||||||
|
public static final int KM_TAG_UNLOCKED_DEVICE_REQUIRED = KM_BOOL | 509;
|
||||||
|
|
||||||
|
public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
|
||||||
|
public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
|
||||||
|
|
||||||
|
public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
|
||||||
|
public static final int KM_TAG_ORIGIN = KM_ENUM | 702;
|
||||||
|
public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
|
||||||
|
public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
|
||||||
|
public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
|
||||||
|
public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_BRAND = KM_BYTES | 710;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_DEVICE = KM_BYTES | 711;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_PRODUCT = KM_BYTES | 712;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_SERIAL = KM_BYTES | 713;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_IMEI = KM_BYTES | 714;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_MANUFACTURER = KM_BYTES | 716;
|
||||||
|
public static final int KM_TAG_ATTESTATION_ID_MODEL = KM_BYTES | 717;
|
||||||
|
public static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION = KM_BOOL | 720;
|
||||||
|
|
||||||
|
public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
|
||||||
|
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
|
||||||
|
public static final int KM_TAG_AUTH_TOKEN = KM_BYTES | 1002;
|
||||||
|
public static final int KM_TAG_MAC_LENGTH = KM_UINT | 1003;
|
||||||
|
|
||||||
|
// Algorithm values.
|
||||||
|
public static final int KM_ALGORITHM_RSA = 1;
|
||||||
|
public static final int KM_ALGORITHM_EC = 3;
|
||||||
|
public static final int KM_ALGORITHM_AES = 32;
|
||||||
|
public static final int KM_ALGORITHM_3DES = 33;
|
||||||
|
public static final int KM_ALGORITHM_HMAC = 128;
|
||||||
|
|
||||||
|
// Block modes.
|
||||||
|
public static final int KM_MODE_ECB = 1;
|
||||||
|
public static final int KM_MODE_CBC = 2;
|
||||||
|
public static final int KM_MODE_CTR = 3;
|
||||||
|
public static final int KM_MODE_GCM = 32;
|
||||||
|
|
||||||
|
// Padding modes.
|
||||||
|
public static final int KM_PAD_NONE = 1;
|
||||||
|
public static final int KM_PAD_RSA_OAEP = 2;
|
||||||
|
public static final int KM_PAD_RSA_PSS = 3;
|
||||||
|
public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
|
||||||
|
public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
|
||||||
|
public static final int KM_PAD_PKCS7 = 64;
|
||||||
|
|
||||||
|
// Digest modes.
|
||||||
|
public static final int KM_DIGEST_NONE = 0;
|
||||||
|
public static final int KM_DIGEST_MD5 = 1;
|
||||||
|
public static final int KM_DIGEST_SHA1 = 2;
|
||||||
|
public static final int KM_DIGEST_SHA_2_224 = 3;
|
||||||
|
public static final int KM_DIGEST_SHA_2_256 = 4;
|
||||||
|
public static final int KM_DIGEST_SHA_2_384 = 5;
|
||||||
|
public static final int KM_DIGEST_SHA_2_512 = 6;
|
||||||
|
|
||||||
|
// Key origins.
|
||||||
|
public static final int KM_ORIGIN_GENERATED = 0;
|
||||||
|
public static final int KM_ORIGIN_IMPORTED = 2;
|
||||||
|
public static final int KM_ORIGIN_UNKNOWN = 3;
|
||||||
|
public static final int KM_ORIGIN_SECURELY_IMPORTED = 4;
|
||||||
|
|
||||||
|
// Key usability requirements.
|
||||||
|
public static final int KM_BLOB_STANDALONE = 0;
|
||||||
|
public static final int KM_BLOB_REQUIRES_FILE_SYSTEM = 1;
|
||||||
|
|
||||||
|
// Operation Purposes.
|
||||||
|
public static final int KM_PURPOSE_ENCRYPT = 0;
|
||||||
|
public static final int KM_PURPOSE_DECRYPT = 1;
|
||||||
|
public static final int KM_PURPOSE_SIGN = 2;
|
||||||
|
public static final int KM_PURPOSE_VERIFY = 3;
|
||||||
|
public static final int KM_PURPOSE_WRAP = 5;
|
||||||
|
|
||||||
|
// Key formats.
|
||||||
|
public static final int KM_KEY_FORMAT_X509 = 0;
|
||||||
|
public static final int KM_KEY_FORMAT_PKCS8 = 1;
|
||||||
|
public static final int KM_KEY_FORMAT_RAW = 3;
|
||||||
|
|
||||||
|
// User authenticators.
|
||||||
|
public static final int HW_AUTH_PASSWORD = 1 << 0;
|
||||||
|
public static final int HW_AUTH_BIOMETRIC = 1 << 1;
|
||||||
|
|
||||||
|
// Error codes.
|
||||||
|
public static final int KM_ERROR_OK = 0;
|
||||||
|
public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_PURPOSE = -2;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_PURPOSE = -3;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_ALGORITHM = -4;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_ALGORITHM = -5;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_DIGEST = -13;
|
||||||
|
public static final int KM_ERROR_INVALID_EXPIRATION_TIME = -14;
|
||||||
|
public static final int KM_ERROR_INVALID_USER_ID = -15;
|
||||||
|
public static final int KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT = -16;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_KEY_FORMAT = -17;
|
||||||
|
public static final int KM_ERROR_INCOMPATIBLE_KEY_FORMAT = -18;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20;
|
||||||
|
public static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
|
||||||
|
public static final int KM_ERROR_KEY_EXPORT_OPTIONS_INVALID = -22;
|
||||||
|
public static final int KM_ERROR_DELEGATION_NOT_ALLOWED = -23;
|
||||||
|
public static final int KM_ERROR_KEY_NOT_YET_VALID = -24;
|
||||||
|
public static final int KM_ERROR_KEY_EXPIRED = -25;
|
||||||
|
public static final int KM_ERROR_KEY_USER_NOT_AUTHENTICATED = -26;
|
||||||
|
public static final int KM_ERROR_OUTPUT_PARAMETER_NULL = -27;
|
||||||
|
public static final int KM_ERROR_INVALID_OPERATION_HANDLE = -28;
|
||||||
|
public static final int KM_ERROR_INSUFFICIENT_BUFFER_SPACE = -29;
|
||||||
|
public static final int KM_ERROR_VERIFICATION_FAILED = -30;
|
||||||
|
public static final int KM_ERROR_TOO_MANY_OPERATIONS = -31;
|
||||||
|
public static final int KM_ERROR_UNEXPECTED_NULL_POINTER = -32;
|
||||||
|
public static final int KM_ERROR_INVALID_KEY_BLOB = -33;
|
||||||
|
public static final int KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED = -34;
|
||||||
|
public static final int KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED = -35;
|
||||||
|
public static final int KM_ERROR_IMPORTED_KEY_NOT_SIGNED = -36;
|
||||||
|
public static final int KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED = -37;
|
||||||
|
public static final int KM_ERROR_INVALID_ARGUMENT = -38;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_TAG = -39;
|
||||||
|
public static final int KM_ERROR_INVALID_TAG = -40;
|
||||||
|
public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41;
|
||||||
|
public static final int KM_ERROR_INVALID_RESCOPING = -42;
|
||||||
|
public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44;
|
||||||
|
public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45;
|
||||||
|
public static final int KM_ERROR_OPERATION_CANCELLED = -46;
|
||||||
|
public static final int KM_ERROR_CONCURRENT_ACCESS_CONFLICT = -47;
|
||||||
|
public static final int KM_ERROR_SECURE_HW_BUSY = -48;
|
||||||
|
public static final int KM_ERROR_SECURE_HW_COMMUNICATION_FAILED = -49;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_EC_FIELD = -50;
|
||||||
|
public static final int KM_ERROR_MISSING_NONCE = -51;
|
||||||
|
public static final int KM_ERROR_INVALID_NONCE = -52;
|
||||||
|
public static final int KM_ERROR_MISSING_MAC_LENGTH = -53;
|
||||||
|
public static final int KM_ERROR_KEY_RATE_LIMIT_EXCEEDED = -54;
|
||||||
|
public static final int KM_ERROR_CALLER_NONCE_PROHIBITED = -55;
|
||||||
|
public static final int KM_ERROR_KEY_MAX_OPS_EXCEEDED = -56;
|
||||||
|
public static final int KM_ERROR_INVALID_MAC_LENGTH = -57;
|
||||||
|
public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58;
|
||||||
|
public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59;
|
||||||
|
public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66;
|
||||||
|
public static final int KM_ERROR_DEVICE_LOCKED = -72;
|
||||||
|
public static final int KM_ERROR_UNIMPLEMENTED = -100;
|
||||||
|
public static final int KM_ERROR_VERSION_MISMATCH = -101;
|
||||||
|
public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
|
||||||
|
|
||||||
|
public static final Map<Integer, String> sErrorCodeToString = new HashMap<Integer, String>();
|
||||||
|
static {
|
||||||
|
sErrorCodeToString.put(KM_ERROR_OK, "OK");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PURPOSE, "Unsupported purpose");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PURPOSE, "Incompatible purpose");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_ALGORITHM, "Unsupported algorithm");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_ALGORITHM, "Incompatible algorithm");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_MAC_LENGTH,
|
||||||
|
"Unsupported MAC or authentication tag length");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_DIGEST, "Incompatible digest");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_EXPIRATION_TIME, "Invalid expiration time");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_USER_ID, "Invalid user ID");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT,
|
||||||
|
"Invalid user authorization timeout");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_FORMAT, "Unsupported key format");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_KEY_FORMAT, "Incompatible key format");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_INPUT_LENGTH, "Invalid input length");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_KEY_NOT_YET_VALID, "Key not yet valid");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_KEY_EXPIRED, "Key expired");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_KEY_USER_NOT_AUTHENTICATED, "Key user not authenticated");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_OPERATION_HANDLE, "Invalid operation handle");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_VERIFICATION_FAILED, "Signature/MAC verification failed");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_TOO_MANY_OPERATIONS, "Too many operations");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_KEY_BLOB, "Invalid key blob");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_ARGUMENT, "Invalid argument");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_MISSING_NONCE, "Required IV missing");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_NONCE, "Invalid IV");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_CALLER_NONCE_PROHIBITED,
|
||||||
|
"Caller-provided IV not permitted");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH,
|
||||||
|
"Invalid MAC or authentication tag length");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_DEVICE_LOCKED, "Device locked");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented");
|
||||||
|
sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getTagType(int tag) {
|
||||||
|
return tag & (0xF << 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getErrorMessage(int errorCode) {
|
||||||
|
String result = sErrorCodeToString.get(errorCode);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return String.valueOf(errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.security.keymaster.KeymasterCertificateChain;
|
||||||
|
|
||||||
|
public interface IKeystoreCertificateChainCallback {
|
||||||
|
void onFinished(KeystoreResponse keystoreResponse, KeymasterCertificateChain keymasterCertificateChain)
|
||||||
|
throws RemoteException;
|
||||||
|
|
||||||
|
public static abstract class Stub {
|
||||||
|
public static IKeystoreCertificateChainCallback asInterface(IBinder b) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.security.keymaster.ExportResult;
|
||||||
|
|
||||||
|
public interface IKeystoreExportKeyCallback {
|
||||||
|
void onFinished(ExportResult exportResult) throws RemoteException;
|
||||||
|
|
||||||
|
public static abstract class Stub {
|
||||||
|
public static IKeystoreExportKeyCallback asInterface(IBinder b) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.IInterface;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.security.keymaster.KeyCharacteristics;
|
||||||
|
|
||||||
|
public interface IKeystoreKeyCharacteristicsCallback extends IInterface {
|
||||||
|
void onFinished(KeystoreResponse keystoreResponse, KeyCharacteristics keyCharacteristics) throws RemoteException;
|
||||||
|
|
||||||
|
public static abstract class Stub {
|
||||||
|
public static IKeystoreKeyCharacteristicsCallback asInterface(IBinder b) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package android.security.keystore;
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import java.lang.String;
|
||||||
|
|
||||||
public interface IKeystoreService {
|
public interface IKeystoreService {
|
||||||
|
public static final String DESCRIPTOR = "android.security.keystore.IKeystoreService";
|
||||||
|
|
||||||
class Stub {
|
class Stub {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package android.security.keystore;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class KeystoreResponse implements Parcelable {
|
||||||
|
public final int error_code_;
|
||||||
|
public final String error_msg_;
|
||||||
|
|
||||||
|
protected KeystoreResponse(int error_code, String error_msg) {
|
||||||
|
this.error_code_ = error_code;
|
||||||
|
this.error_msg_ = error_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<KeystoreResponse> CREATOR = new Creator<KeystoreResponse>() {
|
||||||
|
@Override
|
||||||
|
public KeystoreResponse createFromParcel(Parcel in) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeystoreResponse[] newArray(int size) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user