Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
634a1293c1 | ||
|
|
f115eda2dc | ||
|
|
217edf61fe | ||
|
|
ee5bf2e1a7 | ||
|
|
2181157cb6 | ||
|
|
aa4917e623 | ||
|
|
f06cb30b40 | ||
|
|
03c71bd202 | ||
|
|
7e2fc0b288 | ||
|
|
258a65ba59 |
@@ -242,6 +242,9 @@ boot=device_default
|
|||||||
- **[5ec1cff](https://github.com/5ec1cff/TrickyStore)** — TrickyStore, the project that pioneered keystore interception on Android
|
- **[5ec1cff](https://github.com/5ec1cff/TrickyStore)** — TrickyStore, the project that pioneered keystore interception on Android
|
||||||
- **[LSPlt](https://github.com/LSPosed/LSPlt)** — PLT hook library used for binder interception
|
- **[LSPlt](https://github.com/LSPosed/LSPlt)** — PLT hook library used for binder interception
|
||||||
- **[ring](https://github.com/briansmith/ring)** — Rust cryptography library powering native cert generation
|
- **[ring](https://github.com/briansmith/ring)** — Rust cryptography library powering native cert generation
|
||||||
|
- **[MhmRdd](https://github.com/MhmRdd)** — AOSP compliance improvements via upstream [PR #157](https://github.com/JingMatrix/TEESimulator/pull/157), including authorize_create enforcement, attestation extension alignment, and binder transaction filtering
|
||||||
|
- **[fatalcoder524](https://github.com/fatalcoder524)** — a real contributor and collaborator on this project
|
||||||
|
- **[huguangares](https://github.com/huguangares)** — collaborator and tester
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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 = "v4.8"
|
val verName = "v5.0"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.matrix.TEESimulator"
|
namespace = "org.matrix.TEESimulator"
|
||||||
|
|||||||
@@ -235,19 +235,21 @@ class BinderInterceptor : public BBinder {
|
|||||||
struct RegistrationEntry {
|
struct RegistrationEntry {
|
||||||
wp<IBinder> target;
|
wp<IBinder> target;
|
||||||
sp<IBinder> callback_interface;
|
sp<IBinder> callback_interface;
|
||||||
|
std::vector<uint32_t> filtered_codes;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reader-Writer lock for the registry to allow concurrent reads (lookups)
|
|
||||||
mutable std::shared_mutex registry_mutex_;
|
mutable std::shared_mutex registry_mutex_;
|
||||||
std::map<wp<IBinder>, RegistrationEntry> registry_;
|
std::map<wp<IBinder>, RegistrationEntry> registry_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BinderInterceptor() = default;
|
BinderInterceptor() = default;
|
||||||
|
|
||||||
// Checks if a specific Binder instance is currently registered for interception
|
bool shouldIntercept(const wp<BBinder> &target, uint32_t code) const {
|
||||||
bool isBinderIntercepted(const wp<BBinder> &target) const {
|
|
||||||
std::shared_lock lock(registry_mutex_);
|
std::shared_lock lock(registry_mutex_);
|
||||||
return registry_.find(target) != registry_.end();
|
auto it = registry_.find(target);
|
||||||
|
if (it == registry_.end()) return false;
|
||||||
|
const auto &codes = it->second.filtered_codes;
|
||||||
|
return codes.empty() || std::find(codes.begin(), codes.end(), code) != codes.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main entry point for processing the "Man-in-the-Middle" logic
|
// Main entry point for processing the "Man-in-the-Middle" logic
|
||||||
@@ -393,7 +395,7 @@ void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {
|
|||||||
// This is safe because we are holding a strong reference.
|
// This is safe because we are holding a strong reference.
|
||||||
wp<BBinder> wp_target = target_binder_ptr;
|
wp<BBinder> wp_target = target_binder_ptr;
|
||||||
|
|
||||||
if (g_interceptor_instance->isBinderIntercepted(wp_target)) {
|
if (g_interceptor_instance->shouldIntercept(wp_target, txn_data->code)) {
|
||||||
info.transaction_code = txn_data->code;
|
info.transaction_code = txn_data->code;
|
||||||
info.target_binder = wp_target; // Assign the valid weak pointer
|
info.target_binder = wp_target; // Assign the valid weak pointer
|
||||||
hijack = true;
|
hijack = true;
|
||||||
@@ -538,18 +540,29 @@ status_t BinderInterceptor::handleRegister(const Parcel &data) {
|
|||||||
if (data.readStrongBinder(&callback) != OK || !callback)
|
if (data.readStrongBinder(&callback) != OK || !callback)
|
||||||
return BAD_VALUE;
|
return BAD_VALUE;
|
||||||
|
|
||||||
// We can only intercept local Binders (BBinder), not remote proxies (BpBinder)
|
|
||||||
if (target->localBinder() == nullptr) {
|
if (target->localBinder() == nullptr) {
|
||||||
LOGE("Cannot intercept remote binder proxies.");
|
LOGE("Cannot intercept remote binder proxies.");
|
||||||
return BAD_TYPE;
|
return BAD_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> codes;
|
||||||
|
int32_t code_count = 0;
|
||||||
|
if (data.dataAvail() >= sizeof(int32_t) && data.readInt32(&code_count) == OK && code_count > 0) {
|
||||||
|
codes.reserve(code_count);
|
||||||
|
for (int32_t i = 0; i < code_count; i++) {
|
||||||
|
uint32_t c = 0;
|
||||||
|
if (data.readUint32(&c) == OK) codes.push_back(c);
|
||||||
|
}
|
||||||
|
LOGI("Interceptor registered for binder %p with %zu filtered codes", target.get(), codes.size());
|
||||||
|
} else {
|
||||||
|
LOGI("Interceptor registered for binder %p (all codes)", target.get());
|
||||||
|
}
|
||||||
|
|
||||||
wp<IBinder> weak_target = target;
|
wp<IBinder> weak_target = target;
|
||||||
|
|
||||||
std::unique_lock lock(registry_mutex_);
|
std::unique_lock lock(registry_mutex_);
|
||||||
registry_[weak_target] = {weak_target, callback};
|
registry_[weak_target] = {weak_target, callback, std::move(codes)};
|
||||||
|
|
||||||
LOGI("Interceptor registered for binder %p", target.get());
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package org.matrix.TEESimulator.attestation
|
|||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
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
|
||||||
@@ -127,33 +130,59 @@ object AttestationBuilder {
|
|||||||
return properties
|
return properties
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
|
|
||||||
private fun buildKeyDescription(
|
private fun buildKeyDescription(
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
uid: Int,
|
uid: Int,
|
||||||
securityLevel: Int,
|
securityLevel: Int,
|
||||||
): ASN1Sequence {
|
): ASN1Sequence {
|
||||||
|
val creationTime = System.currentTimeMillis()
|
||||||
val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel)
|
val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel)
|
||||||
val softwareEnforced = buildSoftwareEnforcedList(uid, securityLevel)
|
val softwareEnforced = buildSoftwareEnforcedList(params, uid, securityLevel, creationTime)
|
||||||
|
|
||||||
|
val uniqueId =
|
||||||
|
if (params.includeUniqueId == true && params.attestationChallenge != null) {
|
||||||
|
computeUniqueId(creationTime, createApplicationId(uid).octets)
|
||||||
|
} else {
|
||||||
|
ByteArray(0)
|
||||||
|
}
|
||||||
|
|
||||||
val fields =
|
val fields =
|
||||||
arrayOf(
|
arrayOf(
|
||||||
ASN1Integer(
|
ASN1Integer(AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()),
|
||||||
AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()
|
ASN1Enumerated(securityLevel),
|
||||||
), // attestationVersion
|
ASN1Integer(AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()),
|
||||||
ASN1Enumerated(securityLevel), // attestationSecurityLevel
|
ASN1Enumerated(securityLevel),
|
||||||
ASN1Integer(
|
DEROctetString(params.attestationChallenge ?: ByteArray(0)),
|
||||||
AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()
|
DEROctetString(uniqueId),
|
||||||
), // keymasterVersion
|
|
||||||
ASN1Enumerated(securityLevel), // keymasterSecurityLevel
|
|
||||||
DEROctetString(params.attestationChallenge ?: ByteArray(0)), // attestationChallenge
|
|
||||||
DEROctetString(ByteArray(0)), // uniqueId
|
|
||||||
softwareEnforced,
|
softwareEnforced,
|
||||||
teeEnforced,
|
teeEnforced,
|
||||||
)
|
)
|
||||||
return DERSequence(fields)
|
return DERSequence(fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun computeUniqueId(creationTimeMs: Long, aaidDer: ByteArray): ByteArray {
|
||||||
|
val temporalCounter = creationTimeMs / 2592000000L
|
||||||
|
val message =
|
||||||
|
ByteBuffer.allocate(8 + aaidDer.size + 1)
|
||||||
|
.putLong(temporalCounter)
|
||||||
|
.put(aaidDer)
|
||||||
|
.put(0x00)
|
||||||
|
.array()
|
||||||
|
val mac = Mac.getInstance("HmacSHA256")
|
||||||
|
mac.init(SecretKeySpec(hbk, "HmacSHA256"))
|
||||||
|
return mac.doFinal(message).copyOf(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val hbk: ByteArray by lazy {
|
||||||
|
val file = java.io.File(ConfigurationManager.CONFIG_PATH, "hbk")
|
||||||
|
if (file.exists() && file.length() == 32L) {
|
||||||
|
file.readBytes()
|
||||||
|
} else {
|
||||||
|
SystemLogger.warning("hbk not found, generating ephemeral HBK.")
|
||||||
|
ByteArray(32).also { java.security.SecureRandom().nextBytes(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 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(
|
private fun buildTeeEnforcedList(
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
@@ -194,6 +223,16 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.blockMode.isNotEmpty()) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_BLOCK_MODE,
|
||||||
|
DERSet(params.blockMode.map { ASN1Integer(it.toLong()) }.toTypedArray()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (params.padding.isNotEmpty()) {
|
if (params.padding.isNotEmpty()) {
|
||||||
list.add(
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
@@ -214,14 +253,61 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val attestVersion = AndroidDeviceUtils.getAttestVersion(securityLevel)
|
||||||
|
|
||||||
|
if (params.rsaOaepMgfDigest.isNotEmpty() && attestVersion >= 100) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_RSA_OAEP_MGF_DIGEST,
|
||||||
|
DERSet(params.rsaOaepMgfDigest.map { ASN1Integer(it.toLong()) }.toTypedArray()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.rollbackResistance == true && attestVersion >= 3) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_ROLLBACK_RESISTANCE, DERNull.INSTANCE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.earlyBootOnly == true && attestVersion >= 4) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_EARLY_BOOT_ONLY, DERNull.INSTANCE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.noAuthRequired == true) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.allowWhileOnBody == true) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_ALLOW_WHILE_ON_BODY, DERNull.INSTANCE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.trustedUserPresenceRequired == true && attestVersion >= 3) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_TRUSTED_USER_PRESENCE_REQUIRED, DERNull.INSTANCE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.trustedConfirmationRequired == true && attestVersion >= 3) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_TRUSTED_CONFIRMATION_REQUIRED, DERNull.INSTANCE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
list.addAll(
|
list.addAll(
|
||||||
listOf(
|
listOf(
|
||||||
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE),
|
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_ORIGIN,
|
AttestationConstants.TAG_ORIGIN,
|
||||||
ASN1Integer(0L),
|
ASN1Integer((params.origin ?: 0).toLong()),
|
||||||
), // KeyOrigin.GENERATED
|
),
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_ROOT_OF_TRUST,
|
AttestationConstants.TAG_ROOT_OF_TRUST,
|
||||||
@@ -325,20 +411,32 @@ object AttestationBuilder {
|
|||||||
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
|
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
|
||||||
* Keystore.
|
* Keystore.
|
||||||
*/
|
*/
|
||||||
private fun buildSoftwareEnforcedList(uid: Int, securityLevel: Int): DERSequence {
|
private fun buildSoftwareEnforcedList(
|
||||||
val list =
|
params: KeyMintAttestation,
|
||||||
mutableListOf<ASN1Encodable>(
|
uid: Int,
|
||||||
DERTaggedObject(
|
securityLevel: Int,
|
||||||
true,
|
creationTimeMs: Long = System.currentTimeMillis(),
|
||||||
AttestationConstants.TAG_CREATION_DATETIME,
|
): DERSequence {
|
||||||
ASN1Integer(System.currentTimeMillis()),
|
val list = mutableListOf<ASN1Encodable>()
|
||||||
),
|
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(
|
||||||
|
true,
|
||||||
|
AttestationConstants.TAG_CREATION_DATETIME,
|
||||||
|
ASN1Integer(creationTimeMs),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (params.attestationChallenge != null) {
|
||||||
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
true,
|
true,
|
||||||
AttestationConstants.TAG_ATTESTATION_APPLICATION_ID,
|
AttestationConstants.TAG_ATTESTATION_APPLICATION_ID,
|
||||||
createApplicationId(uid),
|
createApplicationId(uid),
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) {
|
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) {
|
||||||
list.add(
|
list.add(
|
||||||
DERTaggedObject(
|
DERTaggedObject(
|
||||||
@@ -348,7 +446,34 @@ object AttestationBuilder {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return DERSequence(list.toTypedArray())
|
|
||||||
|
params.activeDateTime?.let {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_ACTIVE_DATETIME, ASN1Integer(it.time))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
params.originationExpireDateTime?.let {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_ORIGINATION_EXPIRE_DATETIME, ASN1Integer(it.time))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
params.usageExpireDateTime?.let {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_USAGE_EXPIRE_DATETIME, ASN1Integer(it.time))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
params.usageCountLimit?.let {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_USAGE_COUNT_LIMIT, ASN1Integer(it.toLong()))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (params.unlockedDeviceRequired == true) {
|
||||||
|
list.add(
|
||||||
|
DERTaggedObject(true, AttestationConstants.TAG_UNLOCKED_DEVICE_REQUIRED, DERNull.INSTANCE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -376,6 +501,11 @@ object AttestationBuilder {
|
|||||||
*/
|
*/
|
||||||
@Throws(Throwable::class)
|
@Throws(Throwable::class)
|
||||||
internal fun createApplicationId(uid: Int): DEROctetString {
|
internal fun createApplicationId(uid: Int): DEROctetString {
|
||||||
|
val appUid = uid % 100000
|
||||||
|
if (appUid == 0 || appUid == 1000) {
|
||||||
|
return buildApplicationIdDer(listOf("AndroidSystem" to 1L), emptySet())
|
||||||
|
}
|
||||||
|
|
||||||
val pm =
|
val pm =
|
||||||
ConfigurationManager.getPackageManager()
|
ConfigurationManager.getPackageManager()
|
||||||
?: throw IllegalStateException("PackageManager not found!")
|
?: throw IllegalStateException("PackageManager not found!")
|
||||||
@@ -383,12 +513,11 @@ object AttestationBuilder {
|
|||||||
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
|
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
|
||||||
|
|
||||||
val sha256 = MessageDigest.getInstance("SHA-256")
|
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||||
val packageInfoList = mutableListOf<DERSequence>()
|
val packageInfoList = mutableListOf<Pair<String, Long>>()
|
||||||
val signatureDigests = mutableSetOf<Digest>()
|
val signatureDigests = mutableSetOf<Digest>()
|
||||||
|
|
||||||
// Process all packages associated with the UID in a single loop.
|
val userId = uid / 100000
|
||||||
packages.forEach { packageName ->
|
packages.forEach { packageName ->
|
||||||
val userId = uid / 100000
|
|
||||||
val packageInfo =
|
val packageInfo =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
pm.getPackageInfo(
|
pm.getPackageInfo(
|
||||||
@@ -401,34 +530,36 @@ object AttestationBuilder {
|
|||||||
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId)
|
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add package information (name and version code) to our list.
|
packageInfoList.add(packageInfo.packageName to packageInfo.longVersionCode)
|
||||||
packageInfoList.add(
|
|
||||||
DERSequence(
|
|
||||||
arrayOf(
|
|
||||||
DEROctetString(packageInfo.packageName.toByteArray(StandardCharsets.UTF_8)),
|
|
||||||
ASN1Integer(packageInfo.longVersionCode),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Collect unique signature digests from the signing history.
|
|
||||||
packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature ->
|
packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature ->
|
||||||
val digest = sha256.digest(signature.toByteArray())
|
signatureDigests.add(Digest(sha256.digest(signature.toByteArray())))
|
||||||
signatureDigests.add(Digest(digest))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The application ID is a sequence of two sets:
|
return buildApplicationIdDer(packageInfoList, signatureDigests)
|
||||||
// 1. A set of package information (name and version).
|
}
|
||||||
// 2. A set of SHA-256 digests of the signing certificates.
|
|
||||||
|
private fun buildApplicationIdDer(
|
||||||
|
packages: List<Pair<String, Long>>,
|
||||||
|
digests: Set<Digest>,
|
||||||
|
): DEROctetString {
|
||||||
|
val packageInfoList =
|
||||||
|
packages.map { (name, version) ->
|
||||||
|
DERSequence(
|
||||||
|
arrayOf(
|
||||||
|
DEROctetString(name.toByteArray(StandardCharsets.UTF_8)),
|
||||||
|
ASN1Integer(version),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
val applicationIdSequence =
|
val applicationIdSequence =
|
||||||
DERSequence(
|
DERSequence(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
DERSet(packageInfoList.toTypedArray()),
|
DERSet(packageInfoList.toTypedArray()),
|
||||||
DERSet(signatureDigests.map { DEROctetString(it.digest) }.toTypedArray()),
|
DERSet(digests.map { DEROctetString(it.digest) }.toTypedArray()),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return DEROctetString(applicationIdSequence.encoded)
|
return DEROctetString(applicationIdSequence.encoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,11 @@ object AttestationConstants {
|
|||||||
|
|
||||||
// --- Key Lifetime and Usage Control ---
|
// --- Key Lifetime and Usage Control ---
|
||||||
const val TAG_ROLLBACK_RESISTANCE = 303
|
const val TAG_ROLLBACK_RESISTANCE = 303
|
||||||
|
const val TAG_EARLY_BOOT_ONLY = 305
|
||||||
const val TAG_ACTIVE_DATETIME = 400
|
const val TAG_ACTIVE_DATETIME = 400
|
||||||
const val TAG_ORIGINATION_EXPIRE_DATETIME = 401
|
const val TAG_ORIGINATION_EXPIRE_DATETIME = 401
|
||||||
const val TAG_USAGE_EXPIRE_DATETIME = 402
|
const val TAG_USAGE_EXPIRE_DATETIME = 402
|
||||||
|
const val TAG_MAX_BOOT_LEVEL = 403
|
||||||
const val TAG_MAX_USES_PER_BOOT = 404
|
const val TAG_MAX_USES_PER_BOOT = 404
|
||||||
const val TAG_USAGE_COUNT_LIMIT = 405
|
const val TAG_USAGE_COUNT_LIMIT = 405
|
||||||
|
|
||||||
@@ -56,6 +58,10 @@ object AttestationConstants {
|
|||||||
const val TAG_NO_AUTH_REQUIRED = 503
|
const val TAG_NO_AUTH_REQUIRED = 503
|
||||||
const val TAG_USER_AUTH_TYPE = 504
|
const val TAG_USER_AUTH_TYPE = 504
|
||||||
const val TAG_AUTH_TIMEOUT = 505
|
const val TAG_AUTH_TIMEOUT = 505
|
||||||
|
const val TAG_ALLOW_WHILE_ON_BODY = 506
|
||||||
|
const val TAG_TRUSTED_USER_PRESENCE_REQUIRED = 507
|
||||||
|
const val TAG_TRUSTED_CONFIRMATION_REQUIRED = 508
|
||||||
|
const val TAG_UNLOCKED_DEVICE_REQUIRED = 509
|
||||||
|
|
||||||
// --- Attestation and Application Info ---
|
// --- Attestation and Application Info ---
|
||||||
const val TAG_APPLICATION_ID = 601
|
const val TAG_APPLICATION_ID = 601
|
||||||
|
|||||||
@@ -41,13 +41,29 @@ data class KeyMintAttestation(
|
|||||||
val manufacturer: ByteArray?,
|
val manufacturer: ByteArray?,
|
||||||
val model: ByteArray?,
|
val model: ByteArray?,
|
||||||
val secondImei: ByteArray?,
|
val secondImei: ByteArray?,
|
||||||
|
val activeDateTime: Date?,
|
||||||
|
val originationExpireDateTime: Date?,
|
||||||
|
val usageExpireDateTime: Date?,
|
||||||
|
val usageCountLimit: Int?,
|
||||||
|
val callerNonce: Boolean?,
|
||||||
|
val unlockedDeviceRequired: Boolean?,
|
||||||
|
val includeUniqueId: Boolean?,
|
||||||
|
val rollbackResistance: Boolean?,
|
||||||
|
val earlyBootOnly: Boolean?,
|
||||||
|
val allowWhileOnBody: Boolean?,
|
||||||
|
val trustedUserPresenceRequired: Boolean?,
|
||||||
|
val trustedConfirmationRequired: Boolean?,
|
||||||
|
val noAuthRequired: Boolean?,
|
||||||
|
val maxUsesPerBoot: Int?,
|
||||||
|
val maxBootLevel: Int?,
|
||||||
|
val minMacLength: Int?,
|
||||||
|
val rsaOaepMgfDigest: List<Int>,
|
||||||
) {
|
) {
|
||||||
/** 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(
|
||||||
params: Array<KeyParameter>
|
params: Array<KeyParameter>
|
||||||
) : this(
|
) : this(
|
||||||
// AOSP: [key_param(tag = KEY_SIZE, field = Integer)]
|
keySize = params.findInteger(Tag.KEY_SIZE) ?: params.deriveKeySizeFromCurve(),
|
||||||
keySize = params.findInteger(Tag.KEY_SIZE) ?: 0,
|
|
||||||
|
|
||||||
// AOSP: [key_param(tag = ALGORITHM, field = Algorithm)]
|
// AOSP: [key_param(tag = ALGORITHM, field = Algorithm)]
|
||||||
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
|
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
|
||||||
@@ -100,6 +116,23 @@ data class KeyMintAttestation(
|
|||||||
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),
|
||||||
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
||||||
|
activeDateTime = params.findDate(Tag.ACTIVE_DATETIME),
|
||||||
|
originationExpireDateTime = params.findDate(Tag.ORIGINATION_EXPIRE_DATETIME),
|
||||||
|
usageExpireDateTime = params.findDate(Tag.USAGE_EXPIRE_DATETIME),
|
||||||
|
usageCountLimit = params.findInteger(Tag.USAGE_COUNT_LIMIT),
|
||||||
|
callerNonce = params.findBoolean(Tag.CALLER_NONCE),
|
||||||
|
unlockedDeviceRequired = params.findBoolean(Tag.UNLOCKED_DEVICE_REQUIRED),
|
||||||
|
includeUniqueId = params.findBoolean(Tag.INCLUDE_UNIQUE_ID),
|
||||||
|
rollbackResistance = params.findBoolean(Tag.ROLLBACK_RESISTANCE),
|
||||||
|
earlyBootOnly = params.findBoolean(Tag.EARLY_BOOT_ONLY),
|
||||||
|
allowWhileOnBody = params.findBoolean(Tag.ALLOW_WHILE_ON_BODY),
|
||||||
|
trustedUserPresenceRequired = params.findBoolean(Tag.TRUSTED_USER_PRESENCE_REQUIRED),
|
||||||
|
trustedConfirmationRequired = params.findBoolean(Tag.TRUSTED_CONFIRMATION_REQUIRED),
|
||||||
|
noAuthRequired = params.findBoolean(Tag.NO_AUTH_REQUIRED),
|
||||||
|
maxUsesPerBoot = params.findInteger(Tag.MAX_USES_PER_BOOT),
|
||||||
|
maxBootLevel = params.findInteger(Tag.MAX_BOOT_LEVEL),
|
||||||
|
minMacLength = params.findInteger(Tag.MIN_MAC_LENGTH),
|
||||||
|
rsaOaepMgfDigest = params.findAllDigests(Tag.RSA_OAEP_MGF_DIGEST),
|
||||||
) {
|
) {
|
||||||
// Log all parsed parameters for debugging purposes.
|
// Log all parsed parameters for debugging purposes.
|
||||||
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
||||||
@@ -156,6 +189,21 @@ private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
|
|||||||
private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> =
|
private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> =
|
||||||
this.filter { it.tag == tag }.map { it.value.digest }
|
this.filter { it.tag == tag }.map { it.value.digest }
|
||||||
|
|
||||||
|
private fun Array<KeyParameter>.findBoolean(tag: Int): Boolean? =
|
||||||
|
if (this.any { it.tag == tag }) true else null
|
||||||
|
|
||||||
|
private fun Array<KeyParameter>.deriveKeySizeFromCurve(): Int {
|
||||||
|
val curveId = this.find { it.tag == Tag.EC_CURVE }?.value?.ecCurve ?: return 0
|
||||||
|
return when (curveId) {
|
||||||
|
EcCurve.P_224 -> 224
|
||||||
|
EcCurve.P_256 -> 256
|
||||||
|
EcCurve.P_384 -> 384
|
||||||
|
EcCurve.P_521 -> 521
|
||||||
|
EcCurve.CURVE_25519 -> 256
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives the EC Curve name. Logic: Checks specific EC_CURVE tag first (field=EcCurve), falls back
|
* Derives the EC Curve name. Logic: Checks specific EC_CURVE tag first (field=EcCurve), falls back
|
||||||
* to KEY_SIZE (field=Integer).
|
* to KEY_SIZE (field=Integer).
|
||||||
|
|||||||
@@ -360,7 +360,29 @@ object ConfigurationManager {
|
|||||||
return iPackageManager
|
return iPackageManager
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retrieves the package names associated with a UID. */
|
fun checkSELinuxPermission(callingPid: Int, tclass: String, perm: String): Boolean {
|
||||||
|
return try {
|
||||||
|
val callerCtx =
|
||||||
|
java.io.File("/proc/$callingPid/attr/current").readText().trim('\u0000', ' ', '\n')
|
||||||
|
val selfCtx =
|
||||||
|
java.io.File("/proc/self/attr/current").readText().trim('\u0000', ' ', '\n')
|
||||||
|
android.os.SELinux.checkSELinuxAccess(callerCtx, selfCtx, tclass, perm)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasPermissionForUid(uid: Int, permission: String): Boolean {
|
||||||
|
val userId = uid / 100000
|
||||||
|
return getPackagesForUid(uid).any { pkg ->
|
||||||
|
try {
|
||||||
|
getPackageManager()?.checkPermission(permission, pkg, userId) == 0
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getPackagesForUid(uid: Int): Array<String> {
|
fun getPackagesForUid(uid: Int): Array<String> {
|
||||||
return uidToPackagesCache.getOrPut(uid) {
|
return uidToPackagesCache.getOrPut(uid) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -293,15 +293,21 @@ abstract class BinderInterceptor : Binder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Uses the backdoor binder to register an interceptor for a specific target service. */
|
fun register(
|
||||||
fun register(backdoor: IBinder, target: IBinder, interceptor: BinderInterceptor) {
|
backdoor: IBinder,
|
||||||
|
target: IBinder,
|
||||||
|
interceptor: BinderInterceptor,
|
||||||
|
filteredCodes: IntArray = intArrayOf(),
|
||||||
|
) {
|
||||||
val data = Parcel.obtain()
|
val data = Parcel.obtain()
|
||||||
val reply = Parcel.obtain()
|
val reply = Parcel.obtain()
|
||||||
try {
|
try {
|
||||||
data.writeStrongBinder(target)
|
data.writeStrongBinder(target)
|
||||||
data.writeStrongBinder(interceptor)
|
data.writeStrongBinder(interceptor)
|
||||||
|
data.writeInt(filteredCodes.size)
|
||||||
|
for (code in filteredCodes) data.writeInt(code)
|
||||||
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
|
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
|
||||||
SystemLogger.info("Registered interceptor for target: $target")
|
SystemLogger.info("Registered interceptor for target: $target (${filteredCodes.size} filtered codes)")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("Failed to register binder interceptor.", e)
|
SystemLogger.error("Failed to register binder interceptor.", e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
+3
-2
@@ -68,11 +68,12 @@ abstract class AbstractKeystoreInterceptor : BinderInterceptor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Registers this interceptor with the native hook layer and sets up a death recipient. */
|
protected open val interceptedCodes: IntArray = intArrayOf()
|
||||||
|
|
||||||
private fun setupInterceptor(service: IBinder, backdoor: IBinder) {
|
private fun setupInterceptor(service: IBinder, backdoor: IBinder) {
|
||||||
keystoreService = service
|
keystoreService = service
|
||||||
SystemLogger.info("Registering interceptor for service: $serviceName")
|
SystemLogger.info("Registering interceptor for service: $serviceName")
|
||||||
register(backdoor, service, this)
|
register(backdoor, service, this, interceptedCodes)
|
||||||
service.linkToDeath(createDeathRecipient(), 0)
|
service.linkToDeath(createDeathRecipient(), 0)
|
||||||
onInterceptorReady(service, backdoor)
|
onInterceptorReady(service, backdoor)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package org.matrix.TEESimulator.interception.keystore
|
package org.matrix.TEESimulator.interception.keystore
|
||||||
|
|
||||||
|
import android.hardware.security.keymint.KeyParameter
|
||||||
|
import android.hardware.security.keymint.KeyParameterValue
|
||||||
|
import android.hardware.security.keymint.Tag
|
||||||
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 android.security.keystore.KeystoreResponse
|
||||||
|
import android.system.keystore2.Authorization
|
||||||
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
|
||||||
|
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||||
|
|
||||||
data class KeyIdentifier(val uid: Int, val alias: String)
|
data class KeyIdentifier(val uid: Int, val alias: String)
|
||||||
|
|
||||||
@@ -124,4 +129,53 @@ object InterceptorUtils {
|
|||||||
if (exception != null) reply.setDataPosition(0)
|
if (exception != null) reply.setDataPosition(0)
|
||||||
return exception != null
|
return exception != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createServiceSpecificErrorReply(
|
||||||
|
errorCode: Int
|
||||||
|
): BinderInterceptor.TransactionResult.OverrideReply {
|
||||||
|
val parcel =
|
||||||
|
Parcel.obtain().apply {
|
||||||
|
writeException(android.os.ServiceSpecificException(errorCode))
|
||||||
|
}
|
||||||
|
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun patchAuthorizations(
|
||||||
|
authorizations: Array<Authorization>?,
|
||||||
|
callingUid: Int,
|
||||||
|
): Array<Authorization>? {
|
||||||
|
if (authorizations == null) return null
|
||||||
|
|
||||||
|
val osPatch = AndroidDeviceUtils.getPatchLevel(callingUid)
|
||||||
|
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(callingUid)
|
||||||
|
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(callingUid)
|
||||||
|
|
||||||
|
return authorizations
|
||||||
|
.map { auth ->
|
||||||
|
val replacement =
|
||||||
|
when (auth.keyParameter.tag) {
|
||||||
|
Tag.OS_PATCHLEVEL ->
|
||||||
|
if (osPatch != AndroidDeviceUtils.DO_NOT_REPORT) osPatch else null
|
||||||
|
Tag.VENDOR_PATCHLEVEL ->
|
||||||
|
if (vendorPatch != AndroidDeviceUtils.DO_NOT_REPORT) vendorPatch
|
||||||
|
else null
|
||||||
|
Tag.BOOT_PATCHLEVEL ->
|
||||||
|
if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) bootPatch else null
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (replacement != null) {
|
||||||
|
Authorization().apply {
|
||||||
|
keyParameter =
|
||||||
|
KeyParameter().apply {
|
||||||
|
tag = auth.keyParameter.tag
|
||||||
|
value = KeyParameterValue.integer(replacement)
|
||||||
|
}
|
||||||
|
securityLevel = auth.securityLevel
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toTypedArray()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+118
-24
@@ -5,6 +5,7 @@ import android.hardware.security.keymint.SecurityLevel
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
|
import android.system.keystore2.Domain
|
||||||
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
|
||||||
@@ -45,6 +46,8 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
if (Build.VERSION.SDK_INT >= 34)
|
if (Build.VERSION.SDK_INT >= 34)
|
||||||
InterceptorUtils.getTransactCode(stubBinderClass, "listEntriesBatched")
|
InterceptorUtils.getTransactCode(stubBinderClass, "listEntriesBatched")
|
||||||
else null
|
else null
|
||||||
|
private val GET_NUMBER_OF_ENTRIES_TRANSACTION =
|
||||||
|
InterceptorUtils.getTransactCode(stubBinderClass, "getNumberOfEntries")
|
||||||
|
|
||||||
private val transactionNames: Map<Int, String> by lazy {
|
private val transactionNames: Map<Int, String> by lazy {
|
||||||
stubBinderClass.declaredFields
|
stubBinderClass.declaredFields
|
||||||
@@ -57,11 +60,24 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
|
|
||||||
private const val RESPONSE_KEY_NOT_FOUND = 7
|
private const val RESPONSE_KEY_NOT_FOUND = 7
|
||||||
private val deletedSoftwareKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
private val deletedSoftwareKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||||
|
private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
||||||
|
|
||||||
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
||||||
override val processName = "keystore2"
|
override val processName = "keystore2"
|
||||||
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
|
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
|
||||||
|
|
||||||
|
override val interceptedCodes: IntArray by lazy {
|
||||||
|
listOfNotNull(
|
||||||
|
GET_KEY_ENTRY_TRANSACTION,
|
||||||
|
DELETE_KEY_TRANSACTION,
|
||||||
|
UPDATE_SUBCOMPONENT_TRANSACTION,
|
||||||
|
LIST_ENTRIES_TRANSACTION,
|
||||||
|
LIST_ENTRIES_BATCHED_TRANSACTION,
|
||||||
|
GET_NUMBER_OF_ENTRIES_TRANSACTION,
|
||||||
|
)
|
||||||
|
.toIntArray()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called once the main service is hooked. It proceeds to find and hook the
|
* This method is called once the main service is hooked. It proceeds to find and hook the
|
||||||
* security level sub-services (e.g., TEE, StrongBox).
|
* security level sub-services (e.g., TEE, StrongBox).
|
||||||
@@ -78,7 +94,12 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
SystemLogger.info("Found TEE SecurityLevel. Registering interceptor...")
|
SystemLogger.info("Found TEE SecurityLevel. Registering interceptor...")
|
||||||
val interceptor =
|
val interceptor =
|
||||||
KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
||||||
register(backdoor, tee.asBinder(), interceptor)
|
register(
|
||||||
|
backdoor,
|
||||||
|
tee.asBinder(),
|
||||||
|
interceptor,
|
||||||
|
KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES,
|
||||||
|
)
|
||||||
interceptor.loadPersistedKeys()
|
interceptor.loadPersistedKeys()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +111,12 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
SystemLogger.info("Found StrongBox SecurityLevel. Registering interceptor...")
|
SystemLogger.info("Found StrongBox SecurityLevel. Registering interceptor...")
|
||||||
val interceptor =
|
val interceptor =
|
||||||
KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX)
|
KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX)
|
||||||
register(backdoor, strongbox.asBinder(), interceptor)
|
register(
|
||||||
|
backdoor,
|
||||||
|
strongbox.asBinder(),
|
||||||
|
interceptor,
|
||||||
|
KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES,
|
||||||
|
)
|
||||||
interceptor.loadPersistedKeys()
|
interceptor.loadPersistedKeys()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +132,12 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
|
if (code == GET_NUMBER_OF_ENTRIES_TRANSACTION) {
|
||||||
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
|
||||||
|
return if (ConfigurationManager.shouldSkipUid(callingUid))
|
||||||
|
TransactionResult.ContinueAndSkipPost
|
||||||
|
else TransactionResult.Continue
|
||||||
|
} else if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
|
||||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
|
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
|
||||||
|
|
||||||
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
|
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
|
||||||
@@ -149,29 +180,40 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
data.readTypedObject(KeyDescriptor.CREATOR)
|
data.readTypedObject(KeyDescriptor.CREATOR)
|
||||||
?: return TransactionResult.ContinueAndSkipPost
|
?: return TransactionResult.ContinueAndSkipPost
|
||||||
|
|
||||||
if (descriptor.alias != null) {
|
|
||||||
SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}")
|
|
||||||
} else {
|
|
||||||
SystemLogger.info(
|
|
||||||
"Skip ${transactionNames[code]!!} for key [alias, blob, domain, nspace]: [${descriptor.alias}, ${descriptor.blob}, ${descriptor.domain}, ${descriptor.nspace}]"
|
|
||||||
)
|
|
||||||
return TransactionResult.ContinueAndSkipPost
|
|
||||||
}
|
|
||||||
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
|
||||||
|
|
||||||
if (code == DELETE_KEY_TRANSACTION) {
|
if (code == DELETE_KEY_TRANSACTION) {
|
||||||
val wasSoftwareKey = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) != null
|
val keyId =
|
||||||
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
if (descriptor.alias != null) {
|
||||||
if (wasSoftwareKey) {
|
KeyIdentifier(callingUid, descriptor.alias)
|
||||||
deletedSoftwareKeys.add(keyId)
|
} else if (descriptor.domain == Domain.KEY_ID) {
|
||||||
SystemLogger.info(
|
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(
|
||||||
"[TX_ID: $txId] Deleted cached keypair ${descriptor.alias}, replying with empty response."
|
callingUid, descriptor.nspace
|
||||||
)
|
)?.let { info ->
|
||||||
return InterceptorUtils.createSuccessReply(writeResultCode = false)
|
KeyMintSecurityLevelInterceptor.generatedKeys.entries
|
||||||
|
.find { it.value.nspace == info.nspace && it.key.uid == callingUid }
|
||||||
|
?.key
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
|
||||||
|
if (keyId != null) {
|
||||||
|
val isSoftwareKey =
|
||||||
|
KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(keyId)
|
||||||
|
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
||||||
|
if (isSoftwareKey) {
|
||||||
|
deletedSoftwareKeys.add(keyId)
|
||||||
|
SystemLogger.info(
|
||||||
|
"[TX_ID: $txId] Deleted cached keypair ${keyId.alias}, replying with empty response."
|
||||||
|
)
|
||||||
|
return InterceptorUtils.createSuccessReply(writeResultCode = false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return TransactionResult.ContinueAndSkipPost
|
return TransactionResult.ContinueAndSkipPost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (descriptor.alias == null) {
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
||||||
|
|
||||||
val response = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
|
val response = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
if (deletedSoftwareKeys.remove(keyId)) {
|
if (deletedSoftwareKeys.remove(keyId)) {
|
||||||
@@ -217,7 +259,26 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
if (target != keystoreService || reply == null || InterceptorUtils.hasException(reply))
|
if (target != keystoreService || reply == null || InterceptorUtils.hasException(reply))
|
||||||
return TransactionResult.SkipTransaction
|
return TransactionResult.SkipTransaction
|
||||||
|
|
||||||
if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
|
if (code == GET_NUMBER_OF_ENTRIES_TRANSACTION) {
|
||||||
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
return runCatching {
|
||||||
|
val hardwareCount = reply.readInt()
|
||||||
|
val softwareCount =
|
||||||
|
KeyMintSecurityLevelInterceptor.generatedKeys.keys.count {
|
||||||
|
it.uid == callingUid
|
||||||
|
}
|
||||||
|
val totalCount = hardwareCount + softwareCount
|
||||||
|
val parcel = Parcel.obtain().apply {
|
||||||
|
writeNoException()
|
||||||
|
writeInt(totalCount)
|
||||||
|
}
|
||||||
|
TransactionResult.OverrideReply(parcel)
|
||||||
|
}
|
||||||
|
.getOrElse {
|
||||||
|
SystemLogger.error("[TX_ID: $txId] Failed to modify getNumberOfEntries.", it)
|
||||||
|
TransactionResult.SkipTransaction
|
||||||
|
}
|
||||||
|
} else if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
|
||||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
return runCatching {
|
return runCatching {
|
||||||
@@ -252,6 +313,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!!
|
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!!
|
||||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
|
|
||||||
|
if (userUpdatedKeys.remove(keyId)) {
|
||||||
|
SystemLogger.debug("[TX_ID: $txId] Skipping cert patch for user-updated key $keyId.")
|
||||||
|
return TransactionResult.SkipTransaction
|
||||||
|
}
|
||||||
|
|
||||||
val authorizations = response.metadata.authorizations
|
val authorizations = response.metadata.authorizations
|
||||||
val parsedParameters =
|
val parsedParameters =
|
||||||
KeyMintAttestation(
|
KeyMintAttestation(
|
||||||
@@ -269,6 +335,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
return InterceptorUtils.createTypedObjectReply(response)
|
return InterceptorUtils.createTypedObjectReply(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (KeyMintSecurityLevelInterceptor.importedKeys.contains(keyId)) {
|
||||||
|
SystemLogger.debug("[TX_ID: $txId] Skipping attest-key override for imported key $keyId")
|
||||||
|
return TransactionResult.SkipTransaction
|
||||||
|
}
|
||||||
|
|
||||||
if (parsedParameters.isAttestKey()) {
|
if (parsedParameters.isAttestKey()) {
|
||||||
SystemLogger.warning(
|
SystemLogger.warning(
|
||||||
"[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply."
|
"[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply."
|
||||||
@@ -289,11 +360,13 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
|
|
||||||
keyDescriptor.nspace = SecureRandom().nextLong()
|
keyDescriptor.nspace = SecureRandom().nextLong()
|
||||||
|
response.metadata.key.nspace = keyDescriptor.nspace
|
||||||
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
|
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
|
||||||
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
|
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
|
||||||
keyData.first,
|
keyData.first,
|
||||||
keyDescriptor.nspace,
|
keyDescriptor.nspace,
|
||||||
response,
|
response,
|
||||||
|
parsedParameters,
|
||||||
)
|
)
|
||||||
KeyMintSecurityLevelInterceptor.attestationKeys.add(keyId)
|
KeyMintSecurityLevelInterceptor.attestationKeys.add(keyId)
|
||||||
|
|
||||||
@@ -342,6 +415,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
|
|
||||||
CertificateHelper.updateCertificateChain(response.metadata, finalChain)
|
CertificateHelper.updateCertificateChain(response.metadata, finalChain)
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
|
response.metadata.authorizations =
|
||||||
|
InterceptorUtils.patchAuthorizations(
|
||||||
|
response.metadata.authorizations,
|
||||||
|
callingUid,
|
||||||
|
)
|
||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(response)
|
return InterceptorUtils.createTypedObjectReply(response)
|
||||||
}
|
}
|
||||||
@@ -359,9 +437,25 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
private fun handleUpdateSubcomponent(callingUid: Int, data: Parcel): TransactionResult {
|
private fun handleUpdateSubcomponent(callingUid: Int, data: Parcel): TransactionResult {
|
||||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||||
|
?: return TransactionResult.ContinueAndSkipPost
|
||||||
|
|
||||||
val generatedKeyInfo =
|
val generatedKeyInfo =
|
||||||
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(callingUid, descriptor?.nspace)
|
when (descriptor.domain) {
|
||||||
?: return TransactionResult.ContinueAndSkipPost
|
Domain.KEY_ID ->
|
||||||
|
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(
|
||||||
|
callingUid, descriptor.nspace
|
||||||
|
)
|
||||||
|
Domain.APP ->
|
||||||
|
descriptor.alias?.let {
|
||||||
|
KeyMintSecurityLevelInterceptor.generatedKeys[KeyIdentifier(callingUid, it)]
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generatedKeyInfo == null) {
|
||||||
|
descriptor.alias?.let { userUpdatedKeys.add(KeyIdentifier(callingUid, it)) }
|
||||||
|
return TransactionResult.ContinueAndSkipPost
|
||||||
|
}
|
||||||
|
|
||||||
SystemLogger.info("Updating sub-component with key[${generatedKeyInfo.nspace}]")
|
SystemLogger.info("Updating sub-component with key[${generatedKeyInfo.nspace}]")
|
||||||
val metadata = generatedKeyInfo.response.metadata
|
val metadata = generatedKeyInfo.response.metadata
|
||||||
|
|||||||
+17
@@ -431,6 +431,23 @@ private data class LegacyKeygenParameters(
|
|||||||
manufacturer = null,
|
manufacturer = null,
|
||||||
model = null,
|
model = null,
|
||||||
secondImei = null,
|
secondImei = null,
|
||||||
|
activeDateTime = null,
|
||||||
|
originationExpireDateTime = null,
|
||||||
|
usageExpireDateTime = null,
|
||||||
|
usageCountLimit = null,
|
||||||
|
callerNonce = null,
|
||||||
|
unlockedDeviceRequired = null,
|
||||||
|
includeUniqueId = null,
|
||||||
|
rollbackResistance = null,
|
||||||
|
earlyBootOnly = null,
|
||||||
|
allowWhileOnBody = null,
|
||||||
|
trustedUserPresenceRequired = null,
|
||||||
|
trustedConfirmationRequired = null,
|
||||||
|
noAuthRequired = null,
|
||||||
|
maxUsesPerBoot = null,
|
||||||
|
maxBootLevel = null,
|
||||||
|
minMacLength = null,
|
||||||
|
rsaOaepMgfDigest = emptyList(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package org.matrix.TEESimulator.interception.keystore.shim
|
||||||
|
|
||||||
|
import android.hardware.security.keymint.Algorithm
|
||||||
|
import android.hardware.security.keymint.KeyPurpose
|
||||||
|
import android.hardware.security.keymint.KeyParameter
|
||||||
|
import android.hardware.security.keymint.Tag
|
||||||
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
|
|
||||||
|
object AuthorizeCreate {
|
||||||
|
|
||||||
|
fun check(
|
||||||
|
keyParams: KeyMintAttestation?,
|
||||||
|
opParams: KeyMintAttestation,
|
||||||
|
rawOpParams: Array<KeyParameter>? = null,
|
||||||
|
): Int? {
|
||||||
|
if (keyParams == null) return null
|
||||||
|
return checkPurpose(keyParams, opParams)
|
||||||
|
?: checkAlgorithmPurpose(keyParams, opParams)
|
||||||
|
?: checkTemporalValidity(keyParams, opParams)
|
||||||
|
?: checkCallerNonce(keyParams, rawOpParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkPurpose(keyParams: KeyMintAttestation, opParams: KeyMintAttestation): Int? {
|
||||||
|
val requestedPurpose = opParams.purpose.firstOrNull() ?: return null
|
||||||
|
if (requestedPurpose == KeyPurpose.WRAP_KEY)
|
||||||
|
return KeystoreErrorCodes.incompatiblePurpose
|
||||||
|
if (requestedPurpose !in keyParams.purpose)
|
||||||
|
return KeystoreErrorCodes.incompatiblePurpose
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAlgorithmPurpose(keyParams: KeyMintAttestation, opParams: KeyMintAttestation): Int? {
|
||||||
|
val purpose = opParams.purpose.firstOrNull() ?: return null
|
||||||
|
return when (keyParams.algorithm) {
|
||||||
|
Algorithm.EC -> when (purpose) {
|
||||||
|
KeyPurpose.ENCRYPT, KeyPurpose.DECRYPT -> KeystoreErrorCodes.unsupportedPurpose
|
||||||
|
KeyPurpose.AGREE_KEY -> null
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
Algorithm.RSA -> when (purpose) {
|
||||||
|
KeyPurpose.AGREE_KEY -> KeystoreErrorCodes.unsupportedPurpose
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkTemporalValidity(keyParams: KeyMintAttestation, opParams: KeyMintAttestation): Int? {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val purpose = opParams.purpose.firstOrNull()
|
||||||
|
|
||||||
|
keyParams.activeDateTime?.let { activeDate ->
|
||||||
|
if (now < activeDate.time) return KeystoreErrorCodes.keyNotYetValid
|
||||||
|
}
|
||||||
|
|
||||||
|
keyParams.originationExpireDateTime?.let { expireDate ->
|
||||||
|
if (purpose == KeyPurpose.SIGN || purpose == KeyPurpose.ENCRYPT) {
|
||||||
|
if (now > expireDate.time) return KeystoreErrorCodes.keyExpired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyParams.usageExpireDateTime?.let { expireDate ->
|
||||||
|
if (purpose == KeyPurpose.VERIFY || purpose == KeyPurpose.DECRYPT) {
|
||||||
|
if (now > expireDate.time) return KeystoreErrorCodes.keyExpired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkCallerNonce(keyParams: KeyMintAttestation, rawOpParams: Array<KeyParameter>?): Int? {
|
||||||
|
if (keyParams.callerNonce == true) return null
|
||||||
|
val hasNonce = rawOpParams?.any { it.tag == Tag.NONCE } == true
|
||||||
|
if (hasNonce) return KeystoreErrorCodes.callerNonceProhibited
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
-4
@@ -1,8 +1,10 @@
|
|||||||
package org.matrix.TEESimulator.interception.keystore.shim
|
package org.matrix.TEESimulator.interception.keystore.shim
|
||||||
|
|
||||||
import android.hardware.security.keymint.Algorithm
|
import android.hardware.security.keymint.Algorithm
|
||||||
|
import android.hardware.security.keymint.BlockMode
|
||||||
import android.hardware.security.keymint.EcCurve
|
import android.hardware.security.keymint.EcCurve
|
||||||
import android.hardware.security.keymint.KeyParameter
|
import android.hardware.security.keymint.KeyParameter
|
||||||
|
import android.hardware.security.keymint.KeyPurpose
|
||||||
import android.hardware.security.keymint.KeyParameterValue
|
import android.hardware.security.keymint.KeyParameterValue
|
||||||
import android.hardware.security.keymint.KeyOrigin
|
import android.hardware.security.keymint.KeyOrigin
|
||||||
import android.hardware.security.keymint.SecurityLevel
|
import android.hardware.security.keymint.SecurityLevel
|
||||||
@@ -47,6 +49,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val keyPair: KeyPair,
|
val keyPair: KeyPair,
|
||||||
val nspace: Long,
|
val nspace: Long,
|
||||||
val response: KeyEntryResponse,
|
val response: KeyEntryResponse,
|
||||||
|
val keyParams: KeyMintAttestation? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
||||||
@@ -132,6 +135,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
GeneratedKeyPersistence.delete(keyId)
|
GeneratedKeyPersistence.delete(keyId)
|
||||||
}
|
}
|
||||||
attestationKeys.remove(keyId)
|
attestationKeys.remove(keyId)
|
||||||
|
importedKeys.add(keyId)
|
||||||
} else if (code == CREATE_OPERATION_TRANSACTION) {
|
} else if (code == CREATE_OPERATION_TRANSACTION) {
|
||||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
|
|
||||||
@@ -158,7 +162,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val backdoor = getBackdoor(target)
|
val backdoor = getBackdoor(target)
|
||||||
if (backdoor != null) {
|
if (backdoor != null) {
|
||||||
val interceptor = OperationInterceptor(operation, backdoor)
|
val interceptor = OperationInterceptor(operation, backdoor)
|
||||||
register(backdoor, operationBinder, interceptor)
|
register(backdoor, operationBinder, interceptor, OperationInterceptor.INTERCEPTED_CODES)
|
||||||
interceptedOperations[operationBinder] = interceptor
|
interceptedOperations[operationBinder] = interceptor
|
||||||
} else {
|
} else {
|
||||||
SystemLogger.error(
|
SystemLogger.error(
|
||||||
@@ -267,6 +271,8 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trackAndEnforceOpLimit(callingUid, txId)?.let { return it }
|
||||||
|
|
||||||
SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for uid=$callingUid.")
|
SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for uid=$callingUid.")
|
||||||
|
|
||||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||||
@@ -279,6 +285,11 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthorizeCreate.check(generatedKeyInfo.keyParams, parsedParams, params)?.let { errorCode ->
|
||||||
|
SystemLogger.info("[TX_ID: $txId] authorize_create rejected: errorCode=$errorCode")
|
||||||
|
return InterceptorUtils.createErrorReply(errorCode)
|
||||||
|
}
|
||||||
|
|
||||||
val opLatency = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_OP_LATENCY_FLOOR_MS else 0L
|
val opLatency = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_OP_LATENCY_FLOOR_MS else 0L
|
||||||
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams, opLatency)
|
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams, opLatency)
|
||||||
val maxOps = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_MAX_CONCURRENT_OPS else MAX_CONCURRENT_OPS_PER_UID
|
val maxOps = if (securityLevel == SecurityLevel.STRONGBOX) STRONGBOX_MAX_CONCURRENT_OPS else MAX_CONCURRENT_OPS_PER_UID
|
||||||
@@ -289,6 +300,16 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
CreateOperationResponse().apply {
|
CreateOperationResponse().apply {
|
||||||
iOperation = operationBinder
|
iOperation = operationBinder
|
||||||
operationChallenge = null
|
operationChallenge = null
|
||||||
|
softwareOperation.iv?.let { iv ->
|
||||||
|
parameters = KeyParameters().apply {
|
||||||
|
keyParameter = arrayOf(
|
||||||
|
KeyParameter().apply {
|
||||||
|
tag = Tag.NONCE
|
||||||
|
value = KeyParameterValue.blob(iv)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(response)
|
return InterceptorUtils.createTypedObjectReply(response)
|
||||||
@@ -421,7 +442,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
|
|
||||||
cleanupKeyData(keyId)
|
cleanupKeyData(keyId)
|
||||||
val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor)
|
val response = buildKeyEntryResponse(callingUid, keyData.second, parsedParams, keyDescriptor)
|
||||||
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response)
|
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response, parsedParams)
|
||||||
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
||||||
|
|
||||||
GeneratedKeyPersistence.save(
|
GeneratedKeyPersistence.save(
|
||||||
@@ -608,10 +629,27 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
manufacturer = null,
|
manufacturer = null,
|
||||||
model = null,
|
model = null,
|
||||||
secondImei = null,
|
secondImei = null,
|
||||||
|
activeDateTime = null,
|
||||||
|
originationExpireDateTime = null,
|
||||||
|
usageExpireDateTime = null,
|
||||||
|
usageCountLimit = null,
|
||||||
|
callerNonce = null,
|
||||||
|
unlockedDeviceRequired = null,
|
||||||
|
includeUniqueId = null,
|
||||||
|
rollbackResistance = null,
|
||||||
|
earlyBootOnly = null,
|
||||||
|
allowWhileOnBody = null,
|
||||||
|
trustedUserPresenceRequired = null,
|
||||||
|
trustedConfirmationRequired = null,
|
||||||
|
noAuthRequired = null,
|
||||||
|
maxUsesPerBoot = null,
|
||||||
|
maxBootLevel = null,
|
||||||
|
minMacLength = null,
|
||||||
|
rsaOaepMgfDigest = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = buildKeyEntryResponse(record.uid, certChain, attestation, descriptor)
|
val response = buildKeyEntryResponse(record.uid, certChain, attestation, descriptor)
|
||||||
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, record.nspace, response)
|
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, record.nspace, response, attestation)
|
||||||
if (record.isAttestationKey) attestationKeys.add(keyId)
|
if (record.isAttestationKey) attestationKeys.add(keyId)
|
||||||
|
|
||||||
SystemLogger.debug("Restored persisted key: $keyId")
|
SystemLogger.debug("Restored persisted key: $keyId")
|
||||||
@@ -686,6 +724,9 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
"createOperation",
|
"createOperation",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val INTERCEPTED_CODES =
|
||||||
|
intArrayOf(GENERATE_KEY_TRANSACTION, IMPORT_KEY_TRANSACTION, CREATE_OPERATION_TRANSACTION)
|
||||||
|
|
||||||
private val transactionNames: Map<Int, String> by lazy {
|
private val transactionNames: Map<Int, String> by lazy {
|
||||||
IKeystoreSecurityLevel.Stub::class
|
IKeystoreSecurityLevel.Stub::class
|
||||||
.java
|
.java
|
||||||
@@ -700,6 +741,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
||||||
val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||||
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||||
|
val importedKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||||
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
||||||
|
|
||||||
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
||||||
@@ -728,6 +770,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
if (attestationKeys.remove(keyId)) {
|
if (attestationKeys.remove(keyId)) {
|
||||||
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
||||||
}
|
}
|
||||||
|
importedKeys.remove(keyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeOperationInterceptor(operationBinder: IBinder, backdoor: IBinder) {
|
fun removeOperationInterceptor(operationBinder: IBinder, backdoor: IBinder) {
|
||||||
@@ -752,6 +795,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
generatedKeys.clear()
|
generatedKeys.clear()
|
||||||
patchedChains.clear()
|
patchedChains.clear()
|
||||||
attestationKeys.clear()
|
attestationKeys.clear()
|
||||||
|
importedKeys.clear()
|
||||||
GeneratedKeyPersistence.deleteAll()
|
GeneratedKeyPersistence.deleteAll()
|
||||||
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
||||||
}
|
}
|
||||||
@@ -781,6 +825,7 @@ private fun KeyMintAttestation.toAuthorizations(
|
|||||||
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
||||||
}
|
}
|
||||||
this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
|
this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
|
||||||
|
this.blockMode.forEach { authList.add(createAuth(Tag.BLOCK_MODE, KeyParameterValue.blockMode(it))) }
|
||||||
this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) }
|
this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) }
|
||||||
this.padding.forEach { authList.add(createAuth(Tag.PADDING, KeyParameterValue.paddingMode(it))) }
|
this.padding.forEach { authList.add(createAuth(Tag.PADDING, KeyParameterValue.paddingMode(it))) }
|
||||||
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
||||||
@@ -804,7 +849,16 @@ private fun KeyMintAttestation.toAuthorizations(
|
|||||||
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
|
authList.add(createAuth(Tag.BOOT_PATCHLEVEL, KeyParameterValue.integer(bootPatch)))
|
||||||
}
|
}
|
||||||
authList.add(createAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis())))
|
authList.add(createAuth(Tag.CREATION_DATETIME, KeyParameterValue.dateTime(System.currentTimeMillis())))
|
||||||
authList.add(createAuth(Tag.USER_ID, KeyParameterValue.integer(callingUid / 100000)))
|
authList.add(
|
||||||
|
Authorization().apply {
|
||||||
|
this.keyParameter =
|
||||||
|
KeyParameter().apply {
|
||||||
|
this.tag = Tag.USER_ID
|
||||||
|
this.value = KeyParameterValue.integer(callingUid / 100000)
|
||||||
|
}
|
||||||
|
this.securityLevel = SecurityLevel.SOFTWARE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return authList.toTypedArray()
|
return authList.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -44,6 +44,8 @@ class OperationInterceptor(
|
|||||||
private val ABORT_TRANSACTION =
|
private val ABORT_TRANSACTION =
|
||||||
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "abort")
|
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "abort")
|
||||||
|
|
||||||
|
val INTERCEPTED_CODES = intArrayOf(FINISH_TRANSACTION, ABORT_TRANSACTION)
|
||||||
|
|
||||||
private val transactionNames: Map<Int, String> by lazy {
|
private val transactionNames: Map<Int, String> by lazy {
|
||||||
IKeystoreOperation.Stub::class
|
IKeystoreOperation.Stub::class
|
||||||
.java
|
.java
|
||||||
|
|||||||
+93
-21
@@ -5,7 +5,6 @@ import android.hardware.security.keymint.BlockMode
|
|||||||
import android.hardware.security.keymint.Digest
|
import android.hardware.security.keymint.Digest
|
||||||
import android.hardware.security.keymint.KeyPurpose
|
import android.hardware.security.keymint.KeyPurpose
|
||||||
import android.hardware.security.keymint.PaddingMode
|
import android.hardware.security.keymint.PaddingMode
|
||||||
import android.os.RemoteException
|
|
||||||
import android.os.ServiceSpecificException
|
import android.os.ServiceSpecificException
|
||||||
import android.system.keystore2.IKeystoreOperation
|
import android.system.keystore2.IKeystoreOperation
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
@@ -16,15 +15,16 @@ import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
|||||||
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
|
|
||||||
// A sealed interface to represent the different cryptographic operations we can perform.
|
|
||||||
private sealed interface CryptoPrimitive {
|
private sealed interface CryptoPrimitive {
|
||||||
fun updateAad(aadInput: ByteArray?) {}
|
fun updateAad(aadInput: ByteArray?) {
|
||||||
|
throw ServiceSpecificException(KeystoreErrorCodes.invalidTag)
|
||||||
|
}
|
||||||
fun update(data: ByteArray?): ByteArray?
|
fun update(data: ByteArray?): ByteArray?
|
||||||
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
|
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
|
||||||
fun abort()
|
fun abort()
|
||||||
|
fun getIv(): ByteArray? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper object to map KeyMint constants to JCA algorithm strings.
|
|
||||||
private object JcaAlgorithmMapper {
|
private object JcaAlgorithmMapper {
|
||||||
fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
|
fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
|
||||||
val digest =
|
val digest =
|
||||||
@@ -41,8 +41,9 @@ private object JcaAlgorithmMapper {
|
|||||||
if (isPss) "${digest}withRSA/PSS" else "${digest}withRSA"
|
if (isPss) "${digest}withRSA/PSS" else "${digest}withRSA"
|
||||||
}
|
}
|
||||||
else ->
|
else ->
|
||||||
throw IllegalArgumentException(
|
throw ServiceSpecificException(
|
||||||
"Unsupported signature algorithm: ${params.algorithm}"
|
KeystoreErrorCodes.incompatibleAlgorithm,
|
||||||
|
"Unsupported signature algorithm: ${params.algorithm}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,8 +54,9 @@ private object JcaAlgorithmMapper {
|
|||||||
Algorithm.RSA -> "RSA"
|
Algorithm.RSA -> "RSA"
|
||||||
Algorithm.AES -> "AES"
|
Algorithm.AES -> "AES"
|
||||||
else ->
|
else ->
|
||||||
throw IllegalArgumentException(
|
throw ServiceSpecificException(
|
||||||
"Unsupported cipher algorithm: ${params.algorithm}"
|
KeystoreErrorCodes.incompatibleAlgorithm,
|
||||||
|
"Unsupported cipher algorithm: ${params.algorithm}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val blockMode =
|
val blockMode =
|
||||||
@@ -78,7 +80,6 @@ private object JcaAlgorithmMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concrete implementation for Signing.
|
|
||||||
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
||||||
private val signature: Signature =
|
private val signature: Signature =
|
||||||
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
||||||
@@ -98,7 +99,6 @@ private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimi
|
|||||||
override fun abort() {}
|
override fun abort() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concrete implementation for Verification.
|
|
||||||
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
||||||
private val signature: Signature =
|
private val signature: Signature =
|
||||||
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
||||||
@@ -112,36 +112,43 @@ private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPri
|
|||||||
|
|
||||||
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
if (data != null) update(data)
|
if (data != null) update(data)
|
||||||
if (signature == null) throw SignatureException("Signature to verify is null")
|
if (signature == null) {
|
||||||
|
throw ServiceSpecificException(KeystoreErrorCodes.verificationFailed, "Signature to verify is null")
|
||||||
|
}
|
||||||
if (!this.signature.verify(signature)) {
|
if (!this.signature.verify(signature)) {
|
||||||
// Throwing an exception is how Keystore signals verification failure.
|
throw ServiceSpecificException(KeystoreErrorCodes.verificationFailed, "Signature verification failed")
|
||||||
throw SignatureException("Signature verification failed")
|
|
||||||
}
|
}
|
||||||
// A successful verification returns no data.
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun abort() {}
|
override fun abort() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concrete implementation for Encryption/Decryption.
|
|
||||||
private class CipherPrimitive(
|
private class CipherPrimitive(
|
||||||
keyPair: KeyPair,
|
keyPair: KeyPair,
|
||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
private val opMode: Int,
|
private val opMode: Int,
|
||||||
) : CryptoPrimitive {
|
) : CryptoPrimitive {
|
||||||
|
private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM
|
||||||
private val cipher: Cipher =
|
private val cipher: Cipher =
|
||||||
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
|
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
|
||||||
val key = if (opMode == Cipher.ENCRYPT_MODE) keyPair.public else keyPair.private
|
val key = if (opMode == Cipher.ENCRYPT_MODE) keyPair.public else keyPair.private
|
||||||
init(opMode, key)
|
init(opMode, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateAad(aadInput: ByteArray?) {
|
||||||
|
if (!isAead) throw ServiceSpecificException(KeystoreErrorCodes.invalidTag)
|
||||||
|
if (aadInput != null) cipher.updateAAD(aadInput)
|
||||||
|
}
|
||||||
|
|
||||||
override fun update(data: ByteArray?): ByteArray? =
|
override fun update(data: ByteArray?): ByteArray? =
|
||||||
if (data != null) cipher.update(data) else null
|
if (data != null) cipher.update(data) else null
|
||||||
|
|
||||||
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
|
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
|
||||||
if (data != null) cipher.doFinal(data) else cipher.doFinal()
|
if (data != null) cipher.doFinal(data) else cipher.doFinal()
|
||||||
|
|
||||||
|
override fun getIv(): ByteArray? = if (isAead) cipher.iv else null
|
||||||
|
|
||||||
override fun abort() {}
|
override fun abort() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +162,9 @@ class SoftwareOperation(
|
|||||||
@Volatile var finalized = false
|
@Volatile var finalized = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
val iv: ByteArray?
|
||||||
|
get() = primitive.getIv()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val purpose = params.purpose.firstOrNull()
|
val purpose = params.purpose.firstOrNull()
|
||||||
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
|
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
|
||||||
@@ -167,7 +177,10 @@ class SoftwareOperation(
|
|||||||
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE)
|
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE)
|
||||||
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE)
|
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE)
|
||||||
else ->
|
else ->
|
||||||
throw UnsupportedOperationException("Unsupported operation purpose: $purpose")
|
throw ServiceSpecificException(
|
||||||
|
KeystoreErrorCodes.unsupportedPurpose,
|
||||||
|
"Unsupported operation purpose: $purpose",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +215,7 @@ class SoftwareOperation(
|
|||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
||||||
throw e
|
throw mapToServiceSpecificException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +237,7 @@ class SoftwareOperation(
|
|||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
||||||
throw e
|
throw mapToServiceSpecificException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,13 +247,20 @@ class SoftwareOperation(
|
|||||||
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
|
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun mapToServiceSpecificException(e: Exception): ServiceSpecificException = when (e) {
|
||||||
|
is SignatureException -> ServiceSpecificException(KeystoreErrorCodes.verificationFailed, e.message)
|
||||||
|
is javax.crypto.BadPaddingException -> ServiceSpecificException(KeystoreErrorCodes.invalidArgument, e.message)
|
||||||
|
is javax.crypto.IllegalBlockSizeException -> ServiceSpecificException(KeystoreErrorCodes.invalidInputLength, e.message)
|
||||||
|
is java.security.InvalidKeyException -> ServiceSpecificException(KeystoreErrorCodes.incompatibleKey, e.message)
|
||||||
|
else -> ServiceSpecificException(KeystoreErrorCodes.unknownError, e.message)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// AOSP keystore2 operation.rs: const MAX_RECEIVE_DATA: usize = 0x8000
|
|
||||||
private const val MAX_RECEIVE_DATA = 0x8000
|
private const val MAX_RECEIVE_DATA = 0x8000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private object KeystoreErrorCodes {
|
internal object KeystoreErrorCodes {
|
||||||
val tooMuchData: Int by lazy {
|
val tooMuchData: Int by lazy {
|
||||||
resolveField("android.system.keystore2.ResponseCode", "TOO_MUCH_DATA", 21)
|
resolveField("android.system.keystore2.ResponseCode", "TOO_MUCH_DATA", 21)
|
||||||
}
|
}
|
||||||
@@ -249,7 +269,55 @@ private object KeystoreErrorCodes {
|
|||||||
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_OPERATION_HANDLE", -28)
|
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_OPERATION_HANDLE", -28)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveField(className: String, fieldName: String, fallback: Int): Int =
|
val invalidTag: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_TAG", -76)
|
||||||
|
}
|
||||||
|
|
||||||
|
val verificationFailed: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "VERIFICATION_FAILED", -30)
|
||||||
|
}
|
||||||
|
|
||||||
|
val invalidArgument: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_ARGUMENT", -38)
|
||||||
|
}
|
||||||
|
|
||||||
|
val invalidInputLength: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_INPUT_LENGTH", -21)
|
||||||
|
}
|
||||||
|
|
||||||
|
val incompatibleKey: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_KEY", -31)
|
||||||
|
}
|
||||||
|
|
||||||
|
val incompatiblePurpose: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_PURPOSE", -13)
|
||||||
|
}
|
||||||
|
|
||||||
|
val unsupportedPurpose: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "UNSUPPORTED_PURPOSE", -14)
|
||||||
|
}
|
||||||
|
|
||||||
|
val incompatibleAlgorithm: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_ALGORITHM", -18)
|
||||||
|
}
|
||||||
|
|
||||||
|
val keyNotYetValid: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "KEY_NOT_YET_VALID", -39)
|
||||||
|
}
|
||||||
|
|
||||||
|
val keyExpired: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "KEY_EXPIRED", -40)
|
||||||
|
}
|
||||||
|
|
||||||
|
val callerNonceProhibited: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "CALLER_NONCE_PROHIBITED", -55)
|
||||||
|
}
|
||||||
|
|
||||||
|
val unknownError: Int by lazy {
|
||||||
|
resolveField("android.hardware.security.keymint.ErrorCode", "UNKNOWN_ERROR", -1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveField(className: String, fieldName: String, fallback: Int): Int =
|
||||||
runCatching {
|
runCatching {
|
||||||
Class.forName(className).getField(fieldName).getInt(null)
|
Class.forName(className).getField(fieldName).getInt(null)
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
@@ -261,18 +329,22 @@ private object KeystoreErrorCodes {
|
|||||||
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
|
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
|
||||||
IKeystoreOperation.Stub() {
|
IKeystoreOperation.Stub() {
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun updateAad(aadInput: ByteArray?) {
|
override fun updateAad(aadInput: ByteArray?) {
|
||||||
operation.updateAad(aadInput)
|
operation.updateAad(aadInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun update(input: ByteArray?): ByteArray? {
|
override fun update(input: ByteArray?): ByteArray? {
|
||||||
return operation.update(input)
|
return operation.update(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
|
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||||
return operation.finish(input, signature)
|
return operation.finish(input, signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun abort() {
|
override fun abort() {
|
||||||
operation.abort()
|
operation.abort()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import java.math.BigInteger
|
|||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
import java.security.spec.RSAKeyGenParameterSpec
|
import java.security.spec.RSAKeyGenParameterSpec
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -36,6 +35,8 @@ import org.matrix.TEESimulator.logging.SystemLogger
|
|||||||
*/
|
*/
|
||||||
object CertificateGenerator {
|
object CertificateGenerator {
|
||||||
|
|
||||||
|
private const val UNDEFINED_NOT_AFTER = 253402300799000L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a software-based cryptographic key pair.
|
* Generates a software-based cryptographic key pair.
|
||||||
*
|
*
|
||||||
@@ -49,7 +50,10 @@ object CertificateGenerator {
|
|||||||
Algorithm.EC -> "EC" to ECGenParameterSpec(params.ecCurveName)
|
Algorithm.EC -> "EC" to ECGenParameterSpec(params.ecCurveName)
|
||||||
Algorithm.RSA ->
|
Algorithm.RSA ->
|
||||||
"RSA" to
|
"RSA" to
|
||||||
RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent)
|
RSAKeyGenParameterSpec(
|
||||||
|
params.keySize,
|
||||||
|
params.rsaPublicExponent ?: RSAKeyGenParameterSpec.F4,
|
||||||
|
)
|
||||||
else ->
|
else ->
|
||||||
throw IllegalArgumentException(
|
throw IllegalArgumentException(
|
||||||
"Unsupported algorithm: ${params.algorithm}"
|
"Unsupported algorithm: ${params.algorithm}"
|
||||||
@@ -88,11 +92,9 @@ object CertificateGenerator {
|
|||||||
"Attestation challenge exceeds length limit (${challenge.size} > ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})"
|
"Attestation challenge exceeds length limit (${challenge.size} > ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})"
|
||||||
)
|
)
|
||||||
|
|
||||||
return runCatching {
|
return try {
|
||||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
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) =
|
val (signingKey, issuer) =
|
||||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
||||||
@@ -101,20 +103,20 @@ object CertificateGenerator {
|
|||||||
keybox.keyPair to getIssuerFromKeybox(keybox)
|
keybox.keyPair to getIssuerFromKeybox(keybox)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the new leaf certificate with the simulated attestation.
|
|
||||||
val leafCert =
|
val leafCert =
|
||||||
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
|
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) {
|
if (attestKeyAlias != null) {
|
||||||
listOf(leafCert)
|
listOf(leafCert)
|
||||||
} else {
|
} else {
|
||||||
listOf(leafCert) + keybox.certificates
|
listOf(leafCert) + keybox.certificates
|
||||||
}
|
}
|
||||||
|
} catch (e: android.os.ServiceSpecificException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("Failed to generate certificate chain.", e)
|
||||||
|
null
|
||||||
}
|
}
|
||||||
.onFailure { SystemLogger.error("Failed to generate certificate chain.", it) }
|
|
||||||
.getOrNull()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,7 +130,7 @@ object CertificateGenerator {
|
|||||||
params: KeyMintAttestation,
|
params: KeyMintAttestation,
|
||||||
securityLevel: Int,
|
securityLevel: Int,
|
||||||
): Pair<KeyPair, List<Certificate>>? {
|
): Pair<KeyPair, List<Certificate>>? {
|
||||||
return runCatching {
|
return try {
|
||||||
SystemLogger.info(
|
SystemLogger.info(
|
||||||
"Generating new attested key pair for alias: '$alias' (UID: $uid)"
|
"Generating new attested key pair for alias: '$alias' (UID: $uid)"
|
||||||
)
|
)
|
||||||
@@ -144,11 +146,12 @@ object CertificateGenerator {
|
|||||||
"Successfully generated new certificate chain for alias: '$alias'."
|
"Successfully generated new certificate chain for alias: '$alias'."
|
||||||
)
|
)
|
||||||
Pair(newKeyPair, chain)
|
Pair(newKeyPair, chain)
|
||||||
|
} catch (e: android.os.ServiceSpecificException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SystemLogger.error("Failed to generate attested key pair for alias '$alias'.", e)
|
||||||
|
null
|
||||||
}
|
}
|
||||||
.onFailure {
|
|
||||||
SystemLogger.error("Failed to generate attested key pair for alias '$alias'.", it)
|
|
||||||
}
|
|
||||||
.getOrNull()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIssuerFromKeybox(keybox: KeyBox) =
|
fun getIssuerFromKeybox(keybox: KeyBox) =
|
||||||
@@ -163,7 +166,10 @@ object CertificateGenerator {
|
|||||||
else -> throw IllegalArgumentException("Unsupported algorithm ID: $algorithm")
|
else -> throw IllegalArgumentException("Unsupported algorithm ID: $algorithm")
|
||||||
}
|
}
|
||||||
return KeyBoxManager.getAttestationKey(keyboxFile, algorithmName)
|
return KeyBoxManager.getAttestationKey(keyboxFile, algorithmName)
|
||||||
?: throw Exception("Could not load keybox for UID $uid and algorithm $algorithmName")
|
?: throw android.os.ServiceSpecificException(
|
||||||
|
-75, // ATTESTATION_KEYS_NOT_PROVISIONED
|
||||||
|
"No attestation key for algorithm $algorithmName in $keyboxFile",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retrieves the key pair and issuer name for a given attestation key alias. */
|
/** Retrieves the key pair and issuer name for a given attestation key alias. */
|
||||||
@@ -214,16 +220,15 @@ object CertificateGenerator {
|
|||||||
securityLevel: Int,
|
securityLevel: Int,
|
||||||
): Certificate {
|
): Certificate {
|
||||||
val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key")
|
val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key")
|
||||||
val leafNotAfter =
|
val notBefore = params.certificateNotBefore ?: Date(0)
|
||||||
(signingKeyPair.public as? X509Certificate)?.notAfter
|
val notAfter = params.certificateNotAfter ?: Date(UNDEFINED_NOT_AFTER)
|
||||||
?: Date(System.currentTimeMillis() + 31536000000L)
|
|
||||||
|
|
||||||
val builder =
|
val builder =
|
||||||
JcaX509v3CertificateBuilder(
|
JcaX509v3CertificateBuilder(
|
||||||
issuer,
|
issuer,
|
||||||
params.certificateSerial ?: BigInteger.ONE,
|
params.certificateSerial ?: BigInteger.ONE,
|
||||||
params.certificateNotBefore ?: Date(),
|
notBefore,
|
||||||
params.certificateNotAfter ?: leafNotAfter,
|
notAfter,
|
||||||
subject,
|
subject,
|
||||||
subjectKeyPair.public,
|
subjectKeyPair.public,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.matrix.TEESimulator.util
|
||||||
|
|
||||||
|
import android.hardware.security.keymint.Algorithm
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.util.concurrent.locks.LockSupport
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.exp
|
||||||
|
import kotlin.math.ln
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
object TeeLatencySimulator {
|
||||||
|
|
||||||
|
private val rng = SecureRandom()
|
||||||
|
|
||||||
|
private val sessionBiasMs: Double by lazy { rng.nextGaussian() * 5.0 }
|
||||||
|
private val coldPenaltyMs: Double by lazy { abs(rng.nextGaussian() * 12.0) }
|
||||||
|
|
||||||
|
@Volatile private var firstCall = true
|
||||||
|
|
||||||
|
fun simulateGenerateKeyDelay(algorithm: Int, elapsedNanos: Long) {
|
||||||
|
val elapsedMs = elapsedNanos / 1_000_000.0
|
||||||
|
val targetMs = sampleTotalDelay(algorithm)
|
||||||
|
val remainingMs = targetMs - elapsedMs
|
||||||
|
|
||||||
|
if (remainingMs > 1.0) {
|
||||||
|
LockSupport.parkNanos((remainingMs * 1_000_000).toLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sampleTotalDelay(algorithm: Int): Double {
|
||||||
|
val base = sampleBaseCryptoDelay(algorithm)
|
||||||
|
val transit = sampleExponential(2.5)
|
||||||
|
val jitter = (rng.nextGaussian() * 2.5).coerceIn(-8.0, 12.0)
|
||||||
|
|
||||||
|
var cold = 0.0
|
||||||
|
if (firstCall) {
|
||||||
|
firstCall = false
|
||||||
|
cold = coldPenaltyMs
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(20.0, base + transit + jitter + sessionBiasMs + cold)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sampleBaseCryptoDelay(algorithm: Int): Double {
|
||||||
|
val (mu, sigma) =
|
||||||
|
when (algorithm) {
|
||||||
|
Algorithm.EC -> ln(60.0) to 0.08
|
||||||
|
Algorithm.RSA -> ln(70.0) to 0.08
|
||||||
|
Algorithm.AES -> ln(35.0) to 0.10
|
||||||
|
else -> ln(40.0) to 0.10
|
||||||
|
}
|
||||||
|
return sampleLogNormal(mu, sigma)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sampleLogNormal(mu: Double, sigma: Double): Double {
|
||||||
|
return exp(mu + sigma * rng.nextGaussian())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sampleExponential(mean: Double): Double {
|
||||||
|
var u = rng.nextDouble()
|
||||||
|
while (u == 0.0) u = rng.nextDouble()
|
||||||
|
return -mean * ln(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,65 @@
|
|||||||
|
## TEESimulator-RS v5.0: AOSP Compliance Overhaul
|
||||||
|
|
||||||
|
Major release integrating 30+ AOSP compliance improvements from upstream PR #157 analysis, layered on top of our StrongBox hardening and native cert gen architecture.
|
||||||
|
|
||||||
|
### Attestation Extension Alignment
|
||||||
|
- 17 enforcement tags added to KeyMintAttestation (ACTIVE_DATETIME, ORIGINATION_EXPIRE, USAGE_EXPIRE, USAGE_COUNT_LIMIT, CALLER_NONCE, UNLOCKED_DEVICE_REQUIRED, INCLUDE_UNIQUE_ID, ROLLBACK_RESISTANCE, EARLY_BOOT_ONLY, ALLOW_WHILE_ON_BODY, TRUSTED_USER_PRESENCE_REQUIRED, TRUSTED_CONFIRMATION_REQUIRED, NO_AUTH_REQUIRED, MAX_USES_PER_BOOT, MAX_BOOT_LEVEL, MIN_MAC_LENGTH, RSA_OAEP_MGF_DIGEST)
|
||||||
|
- BLOCK_MODE encoded as SET OF INTEGER per AOSP attestation_record.h
|
||||||
|
- Version-guarded tags (RSA_OAEP_MGF_DIGEST >=100, ROLLBACK_RESISTANCE >=3, EARLY_BOOT_ONLY >=4)
|
||||||
|
- INCLUDE_UNIQUE_ID computed via HMAC-SHA256 per KeyMint HAL spec using device HBK
|
||||||
|
- AAID gated on attestation challenge presence
|
||||||
|
- Certificate validity defaults aligned with AOSP (epoch notBefore, 9999-12-31 notAfter)
|
||||||
|
|
||||||
|
### Binder Infrastructure
|
||||||
|
- Native transaction code filtering at C++ level, skipping JNI for non-intercepted codes
|
||||||
|
- getNumberOfEntries includes software-generated key count
|
||||||
|
- deleteKey resolves KEY_ID domain via generatedKeys lookup
|
||||||
|
- patchAuthorizations for OS/VENDOR/BOOT patch levels in authorization arrays
|
||||||
|
|
||||||
|
### Software Operation AOSP Conformance
|
||||||
|
- updateAad on non-AEAD operations returns INVALID_TAG (-76), matching AOSP operation.rs
|
||||||
|
- All crypto exceptions wrapped as ServiceSpecificException with correct KeyMint error codes
|
||||||
|
- GCM IV returned in CreateOperationResponse.parameters for encrypt operations
|
||||||
|
- SoftwareOperationBinder methods @Synchronized, matching AOSP Mutex per operation
|
||||||
|
- authorize_create enforcement: PURPOSE validation, algorithm-purpose compatibility, temporal constraints, CALLER_NONCE prohibition, WRAP_KEY rejection
|
||||||
|
|
||||||
|
### Security and Configuration
|
||||||
|
- SELinux permission checks via /proc/pid/attr/current
|
||||||
|
- Per-UID permission verification through IPackageManager.checkPermission
|
||||||
|
- Imported key tracking prevents stale attest-key overrides in getKeyEntry
|
||||||
|
- nspace consistency fix in attest-key override path
|
||||||
|
- TeeLatencySimulator with log-normal distribution matching real hardware profiles
|
||||||
|
- Device-unique HBK seed generated on install (32 bytes from /dev/random)
|
||||||
|
|
||||||
|
### Preserved from v4.8
|
||||||
|
- StrongBox op limits (4 concurrent max, TOO_MANY_OPERATIONS rejection)
|
||||||
|
- LRU operation pruning per security level
|
||||||
|
- Hardware keygen rate limiting (2/30s sliding window, 2 concurrent cap)
|
||||||
|
- Native Rust cert generation with BouncyCastle fallback
|
||||||
|
- Key persistence across reboots
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TEESimulator-RS v4.8.1: StrongBox Op Rejection Fix
|
||||||
|
|
||||||
|
- **StrongBox op limit gate fix** — `trackAndEnforceOpLimit` was only called in the `Domain.KEY_ID` not-found path, so software-generated keys (found via `Domain.APP`) bypassed `STRONGBOX_MAX_CONCURRENT_OPS=4` entirely. DuckDetector's concurrent signing handles test created 24+ operations that all succeeded via LRU pruning instead of being rejected with `TOO_MANY_OPERATIONS (-29)`. Now enforced for all StrongBox createOperation paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TEESimulator-RS v4.8: StrongBox Hardening & LRU Pruning
|
||||||
|
|
||||||
|
Tested against DuckDetector on OnePlus (Android 16, KSU). Tamper score dropped from 32 to 8.
|
||||||
|
|
||||||
|
- **LRU operation pruning** — Concurrent software operations capped at 15 per UID (TEE) and 4 per UID (StrongBox), with oldest-first eviction. Pruned operations return `INVALID_OPERATION_HANDLE (-28)`, matching AOSP keystore2 malus-based pruning.
|
||||||
|
- **StrongBox param guard** — Unsupported StrongBox params (RSA >2048-bit, non-P256 EC curves) forwarded to real HAL for proper rejection instead of generating in software.
|
||||||
|
- **StrongBox timing** — Key generation floors at 250ms, signing at 80ms on StrongBox security level to match real secure element latency.
|
||||||
|
- **StrongBox op limit** — Sliding-window enforcer caps concurrent StrongBox operations for both software and hardware key paths, returning `TOO_MANY_OPERATIONS (-29)` when exceeded.
|
||||||
|
- **ECDSA algorithm alias** — Accept "ECDSA" in addition to "EC" as JCA private key algorithm name. Fixes SIGSEGV crash on Android 10 devices where the provider reports EC keys as "ECDSA". Closes #4.
|
||||||
|
- **createOperation domain handling** — Software-generated keys now found via both `Domain.APP` (alias) and `Domain.KEY_ID` (nspace) lookup paths.
|
||||||
|
- **Permission guards** — Device ID attestation tags (IMEI, MEID, serial) require caller permission checks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## TEESimulator-RS v4.7: Operation & Attestation Fixes
|
## TEESimulator-RS v4.7: Operation & Attestation Fixes
|
||||||
|
|
||||||
Tested against [KeyDetector](https://github.com/XiaoTong6666/KeyDetector) and [Key Attestation](https://github.com/nickel-lang/nickel) on OnePlus (Android 16) and Xiaomi Redmi 14C (Android 14).
|
Tested against [KeyDetector](https://github.com/XiaoTong6666/KeyDetector) and [Key Attestation](https://github.com/nickel-lang/nickel) on OnePlus (Android 16) and Xiaomi Redmi 14C (Android 14).
|
||||||
|
|||||||
@@ -91,3 +91,10 @@ if [ ! -f "$CONFIG_DIR/target.txt" ]; then
|
|||||||
ui_print "- Adding default target scope"
|
ui_print "- Adding default target scope"
|
||||||
install_file "target.txt" "$CONFIG_DIR"
|
install_file "target.txt" "$CONFIG_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
rm -f "$CONFIG_DIR/tee_status.txt"
|
||||||
|
|
||||||
|
if [ ! -f "$CONFIG_DIR/hbk" ]; then
|
||||||
|
ui_print "- Generating device-unique hardware-bound key seed"
|
||||||
|
head -c 32 /dev/random > "$CONFIG_DIR/hbk"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ public interface IPackageManager {
|
|||||||
|
|
||||||
ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId);
|
ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId);
|
||||||
|
|
||||||
|
int checkPermission(String permName, String pkgName, int userId);
|
||||||
|
|
||||||
class Stub {
|
class Stub {
|
||||||
public static IPackageManager asInterface(IBinder binder) {
|
public static IPackageManager asInterface(IBinder binder) {
|
||||||
throw new UnsupportedOperationException("STUB!");
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package android.os;
|
||||||
|
|
||||||
|
public class SELinux {
|
||||||
|
public static boolean checkSELinuxAccess(
|
||||||
|
String scon, String tcon, String tclass, String perm) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,10 @@ public class ServiceManager {
|
|||||||
throw new UnsupportedOperationException("STUB!");
|
throw new UnsupportedOperationException("STUB!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isDeclared(String name) {
|
||||||
|
throw new UnsupportedOperationException("STUB!");
|
||||||
|
}
|
||||||
|
|
||||||
public static String[] listServices() {
|
public static String[] listServices() {
|
||||||
throw new UnsupportedOperationException("STUB!");
|
throw new UnsupportedOperationException("STUB!");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user