resetprop overrides for ro.boot.* props don't survive reboots. On
devices where the kernel doesn't set ro.boot.vbmeta.public_key_digest,
the fallback chain hit random generation on every boot, producing a
different RootOfTrust hash each time.
Added file-based persistence (boot_hash.bin, boot_key.bin) as a
fallback layer between TEE cache and random generation. Once a value
is determined from any source, it's written to disk and reused on
subsequent boots.
Verified on Redmi 14C: second boot reads from persistent file instead
of regenerating random bytes.
DuckDetector flagged two issues:
1. Oversized challenge accepted, 256-byte attestation challenge should
return INVALID_INPUT_LENGTH (-21) like real KeyMint. Added early check
in handleGenerateKey before any path decision.
2. Issuer/subject chain mismatch, rcgen's HashMap loses DN attribute
ordering and converts PrintableString to UTF8String, producing
different DER bytes. Replaced rcgen with manual DER assembly that
injects raw keybox issuer_dn_der bytes directly.
Verified on device: TX_ID 315 rejects 256-byte challenge, TX_ID 501
generates valid 4-cert chain with correct issuer linkage.
Matches the existing verifiedBootKey null-zero guard. When the TEE
returns a zeroed hash, fall through to the system property or random
fallback instead of embedding a detectable all-zero value.
Two changes to Keystore2Interceptor:
1. Hardware attest keys created before TEESimulator loads now get
detected in the getKeyEntry post-hook via isAttestKey(). A software
replacement keypair is generated, cached, and persisted so the
unpatched hardware chain is never served.
2. GMS calls listEntries frequently. Skip the post-hook injection
for com.google.android.gms to reduce log flooding and unnecessary
key merging work.
Also adds null-alias guard in onPreTransact to avoid NPE on keys
looked up by domain/nspace without an alias.
Parse ORIGIN tag from KeyParameter array into KeyMintAttestation
data class. Add isAttestKey() and isImportKey() convenience methods
to consolidate purpose/origin checks scattered across interceptors.
BouncyCastle DERSet() sorts by full encoded sequence, but AOSP
keystore2 maintenance.rs sorts by encoded name only. Replace
PackageManager-based APEX enumeration with filesystem scan of
/apex/ directories using a minimal protobuf parser for
apex_manifest.pb. Encode the DER SET tag manually to preserve
the name-only sort order.
Make logging init idempotent (swallow SetGlobalDefaultError on repeat
call), remove unused dumpLogs JNI params that violated the API contract,
and strip dead public_key_spki field + build_ec_spki() that were
computed on every keygen but never consumed by the cert builder.
cargo-ndk builds libcertgen.so for arm64-v8a during prepareModuleFiles.
AGP mergeJniLibFolders picks up jniLibs/ and routes through
stripped_native_libs into the module ZIP. customize.sh extracts the .so
on device install. ProGuard keeps NativeCertGen JNI class and
CertGenConfig fields for runtime JNI field access.
NativeCertGen.kt provides CertGenConfig data class and JNI bridge
to libcertgen.so. KeyMintSecurityLevelInterceptor.doSoftwareKeyGen()
tries native path first, falls back to BouncyCastle on failure or
when library unavailable. App.kt loads libcertgen.so at daemon start.
Remove ENCRYPT/VERIFY from KeyUsage mapping to match Kotlin behavior.
Document BasicConstraints and SKI suppression via rcgen NoCa default.
Fix rotating log off-by-one that kept one extra backup file. Handle
BMPString (UTF-16BE) and VisibleString in X.500 DN parser.
Builds v3 leaf certificate with attestation extension and KeyUsage,
signs with keybox private key via rcgen 0.13.2. Assembles full chain
(leaf + keybox intermediates + root). Supports EC and RSA keybox
signing keys. Uses rcgen's signed_by() with a synthesized issuer
Certificate, no manual DER fallback needed.
Multi-output logging via tracing: /dev/kmsg for logcat, rotating file
appender (512KB, 3 files), stderr for debug. Diagnostic ZIP dump with
log files and TEE status snapshots. Verbose toggle via JNI flag or
.verbose marker file.
DER encoder for Android KeyMint attestation extension (OID
1.3.6.1.4.1.11129.2.1.17). SecurityLevel and VerifiedBootState as
ENUMERATED, EXPLICIT context-specific tagging with long-form for
tags >= 31, sorted AuthorizationList fields, RootOfTrust with
BOOLEAN TRUE=0xFF, SET OF INTEGER with DER sort, DO_NOT_REPORT
sentinel omission.
EC keygen now returns proper SPKI DER instead of raw point bytes.
RSA keygen uses caller-supplied exponent via new_with_exp() and
validates key size to 2048/3072/4096. Keybox parser extracts leaf
subject DN (not issuer). Added AttestKey=7 to KeyPurpose. Realigned
error variants with spec.
Splits concatenated DER cert chains into individual certificates,
extracts leaf issuer DN and notAfter via x509-cert crate. Handles
multi-byte DER length encoding (0x81-0x84).
Cargo.toml with 16 dependencies per build spec, error types with
From impls for all upstream error types, CertGenParams mapping the
full JNI config contract, EC/RSA key generation via ring and rsa crates.
Compiles clean for aarch64-linux-android via cargo-ndk.
TrickyAddon fetches Pixel bulletin dates for boot/vendor but system=prop
resolves to the real device prop, creating a cross-component date mismatch
on non-Pixel devices. Force all three through the same prop resolution path.
Sliding window limits each UID to 2 hardware generateKey calls per
30s burst window with max 2 concurrent. Overflow falls back to
software cert generation.
importKey post-hook retains patched chains instead of full eviction,
preventing detectors from using generate-then-import to bypass
attestation patching. getKeyEntry serves retained chains for imported
keys that overwrote attested aliases.
Some upstream keybox sources inject HTML comments inside PEM
certificate blocks. BouncyCastle's PEMParser chokes on these
non-base64 lines, silently failing to load the keybox.
Filter lines starting with <!-- in trimLines() before the content
reaches the PEM parser.
When a config file is deleted, the event handler sets file=null but
then force-unwraps it with file!! in the when block, crashing the
FileObserver thread. All subsequent config change notifications are
silently lost.
Replace force-unwrap with safe call, log a warning on deletion.
Fork-based supervisor ensures the interceptor process survives crashes.
pingBinder() liveness check on pre-transact returns DEAD_OBJECT to
callers when interceptor is down, preventing real TEE state from leaking
during the restart window.
action.sh clears persistent key storage via KSU Action button.
uninstall.sh kills daemon processes and removes module artifacts while
preserving target.txt and keybox configuration.
The previous implementation hardcoded the X.509 KeyUsage extension to `keyCertSign` for all generated certificates. This was only correct for keys with the `ATTEST_KEY` purpose and violated the Android HAL specification for keys intended for other uses. For instance, a key created for signing (`KeyPurpose::SIGN`) requires the `digitalSignature` bit to be set, not `keyCertSign`.
This commit corrects the logic by dynamically constructing the `KeyUsage` bitmask from the key's specified purposes, adhering to the mapping defined in `KeyCreationResult.aidl`. This ensures that generated certificates now have the correct KeyUsage bits, accurately reflecting the key's intended function (e.g., signing, decryption, key wrapping) and making them compliant with the specification.
This merge addresses a critical strong reference leak in the ioctl hook that occurred during binder transaction interception. The leak was caused by a double increment of the reference count, once manually and once by a smart pointer's constructor, with only a single corresponding decrement. The fix ensures a balanced increment and decrement, preventing the leak and subsequent crashes.
Additionally, this change:
- Reverts a now-unnecessary compatibility layer for the Android 11 RefBase ABI.
- Implements `getInterfaceDescriptor` in the `BinderStub` to silence framework warnings that appeared after the primary leak was fixed.
The previous code incorrectly assumed `takeIf` prevents the execution of the receiver statement. Since `takeIf` is an extension function, the receiver, `InterceptorUtils.getTransactCode`, was evaluated eagerly *before* the version check predicate could run.
This commit replaces the `takeIf` chain with a standard `if/else` block to ensure the reflection call is only executed when the API level supports it.
Additionally, repeated `IKeystoreService.Stub::class.java` references were refactored into a `stubBinderClass` property.
Previously, generated keys were functional but invisible to enumeration APIs like `KeyStore.aliases()`. Because these keys reside solely in the simulator's memory, the standard database query performed by the system Keystore does not return them.
This commit intercepts `listEntries` and `listEntriesBatched` to inject these generated keys into the results.
Key implementation details:
- ListEntriesHandler: Encapsulates the logic to merge hardware-backed keys with software-backed keys.
- Ordering: Uses a `TreeMap` to ensure merged results are lexicographically sorted, mimicking AOSP behavior.
- Binder Safety: Implements `estimateSafeAmountToReturn` to calculate the response size. The handler truncates the result list if it exceeds the binder transaction limit (~350KB) as done in AOSP.
- Pagination: Respects the `startPastAlias` parameter to support batched listing.
Co-authored-by: JingMatrix <jingmatrix@gmail.com>
Relocate the `attestationChallenge` length validation from `generateSoftwareKeyPair` to `generateCertificateChain`.
The challenge is only utilized during the construction of the certificate chain (via `AttestationBuilder.buildKeyDescription`). Placing the check in the key pair generation stage caused the logic to miss the `attestKey` transaction hook in `KeystoreInterceptor`.
This fixes a bug introduced in d77508d0c1 which missed the detection bypass for Android 10 and 11 devices.
Observed an abnormal Keymaster attestation structure on certain Android 11 devices where the `softwareEnforced` and `teeEnforced` authorization lists were swapped in order. This is a deviation from the documented specification and the behavior seen on most devices.
This non-compliance caused parsing failures, as the code expected the `teeEnforced` list to be at a fixed index (7). On the affected devices, this index contained the `softwareEnforced` list, which critically lacks the `TAG_ROOT_OF_TRUST` needed for successful validation and patching.
This commit introduces a defensive normalization step to handle this device-specific anomaly gracefully:
1. Before parsing, the code now inspects the ASN.1 sequence at the expected `softwareEnforced` index (6).
2. It checks for the presence of the `TAG_ROOT_OF_TRUST`, which can only exist in the TEE-enforced list.
3. If the tag is found, the code concludes the lists are swapped and corrects the `allFields` array in-place by swapping the elements at indices 6 and 7.
By normalizing the data structure at the beginning, the rest of the parsing and patching logic can proceed without modification, ensuring correct operation on both compliant and non-compliant devices.
The `extractAlias` utility was failing to strip `USRCERT_` and `CACERT_` prefixes, causing a cache miss during certificate chain patching. The function is now updated to correctly handle these prefixes.
Moreover, more logs are added to help debugging in the future.
Allow `keystore` to access the `file` class for `adb_data_file` and `shell_data_file` contexts.
The target contexts correspond to the following locations:
- `adb_data_file`: The library path `/data/adb/modules/tricky_store/libTEESimulator.so`, used for FD transfer.
- `shell_data_file`: The fallback mechanism for loading the library by staging it in `/data/local/tmp`.
Note: The rule for the `dir` class (directory search) has been removed because the supporting audit logs were lost. The remaining file access logs were observed on a MEIZU 21 Note.
This resolves several critical stability issues observed on Android 12 devices, including race conditions and API compatibility problems.
Key changes include:
- Resolves Race Condition in TEE Check:
Fixes a NullPointerException that occurred when the TEE functionality check was executed before the PackageManagerService was ready. The code now explicitly waits for the package manager to become available, preventing the crash on startup.
- Fixes IllegalStateException on Initialization:
Eliminates a crash caused by `setTelephonyServiceManager called twice`. This was due to a redundant call to `initializeMainlineModules()` in the DeviceAttestationService, which is now correctly handled a single time during application startup.
- Fixes NoSuchAlgorithmException in Attestation:
Adds a normalization function to handle signature algorithm names reported in all-caps by older Android versions (e.g., "SHA256WITHECDSA"). This ensures compatibility with Bouncy Castle, which expects a specific casing (e.g., "SHA256withECDSA").
When the TEESimulator is configured to intercept UID 1000, accessing the `lazy` `bootKey` property causes a StackOverflowError.
The property's initializer sends a key generation request (UID 0) to probe real hardware. Previously, the C++ layer hijacked this request and spoofed it to UID 1000. This sent the request back to the Kotlin interceptor (if configured so), which attempted to access `bootKey` again to build the response, creating an infinite loop.
This change spoofs UID 0 requests to 1000 (to pass Keystore permissions) but explicitly bypasses hijacking, ensuring the probe request hits the real hardware.
After few tests in various devices, it seems that SELinux context modifications are unnecessary for the injection to work.
We thus remove all related manipulations. Further (partial) reverting of the commit must be justified with SELinux logs:
> adb shell su -c 'cat /proc/kmsg | grep avc'
- Implement fallbacks to `PTRACE_GETREGS` and `PTRACE_SETREGS` for 32-bit ARM (`__arm__`). Some kernels return `EIO` or `EINVAL` when attempting to access `NT_PRSTATUS` via `PTRACE_GETREGSET`/`PTRACE_SETREGSET`.
- Update `transfer_fd_to_remote` to use `libc_return_addr` instead of `0` as the return address during the `recvmsg` split-call. This ensures the remote process stops predictably at a known non-executable location rather than relying on a potentially unsafe jump to `0x0`.
- Clarify comments regarding i386 argument passing in `utils.cpp`. Correctly note that a linear `write_proc` starting at the new SP matches the `cdecl` Right-to-Left memory layout (since stacks grow downwards while memory writes move upwards), removing the suggestion that arguments needed reversing.
Android allows applications to generate a new key using an existing alias without explicitly calling `deleteKey` first. In this scenario, the new key effectively replaces the old one. As a simulator, we must strictly follow this logic to prevent returning stale data.
Previously, `KeyMintSecurityLevelInterceptor` did not enforce mutual exclusion between the software key cache (`generatedKeys`) and the hardware chain cache (`patchedChains`). This led to state desynchronization where a stale software key could shadow a newly patched hardware chain if the alias was reused.
This change ensures `cleanupKeyData` is invoked immediately before caching a new key / chain in both the software (`handleGenerateKey`) and hardware (`onPostTransact`) paths, ensuring the simulator returns the correct key for the most recent generation request.
The previous implementation attempted to retrieve `moduleHash` from the `softwareEnforced` sequence using a hardcoded index (index 2).
However, fields in the Key Attestation `AuthorizationList` are optional. In observed crashes, index 2 actually corresponded to `keySize` (Tag 3, ASN1Integer) rather than `moduleHash`, causing an `IllegalArgumentException` when the code attempted to parse it as an `ASN1OctetString`.
This commit replaces the index-based access with a dynamic lookup for Tag 724.
- Strictly adhere to the System V AMD64 ABI by skipping the 128-byte "Red Zone" before modifying the stack, see page 23 of https://gitlab.com/x86-psABIs/x86-64-ABI/-/jobs/artifacts/master/raw/x86-64-ABI/abi.pdf?job=build for details.
- Added `inject_via_staging` as a fallback strategy:
1. Copies the payload to `/data/local/tmp`.
2. Sets permissions/context (`u:object_r:system_file:s0`).
3. Loads via standard `dlopen`.
4. Immediately unlinks the file for stealth.
- Introduced `RegisterRestorer` RAII class to guarantee original registers are restored even if the injection logic returns early due to error.