Symmetric keys (AES/HMAC/3DES) don't have KeyPairs or attestation
certs, routing them through doSoftwareKeyGen crashes with
"Unsupported algorithm: 32". Skip the software path entirely and
let the real HAL handle them.
Also adds CTR block mode, RSA_PKCS1_1_5_SIGN cipher padding, and
RSA_PSS signature padding to JcaAlgorithmMapper.
1. Add permission checks for KeyMintSecurityLevelInterceptor to ensure that only authorized users can access sensitive information about the security level of the key mint.
2. Fix regression where device id attestation was allowed for all users by adding appropriate permission checks.
3. Update .gitignore to exclude build artifacts and generated files to keep the repository clean and prevent accidental commits of unnecessary files.
KeyDetector's OperationErrorPathChecker (flag 0x400000) probes three
error-path behaviors that real keystore2 operations expose. Our
SoftwareOperationBinder was missing all three, plus had no updateAad
implementation which caused AbstractMethodError on Android 16 where
the runtime Stub declares it abstract.
SoftwareOperation changes:
- Add finalized state tracking; post-abort calls now throw
INVALID_OPERATION_HANDLE (-28) matching AOSP operation.rs
- Add input length guard (0x8000) throwing TOO_MUCH_DATA (29)
matching AOSP operation.rs MAX_RECEIVE_DATA
- Add updateAad to CryptoPrimitive interface and SoftwareOperationBinder
- Add KeystoreErrorCodes with runtime reflection + AOSP fallback values
KeyMintSecurityLevelInterceptor changes:
- Infer algorithm from stored key pair when operation params omit
ALGORITHM tag, matching AOSP behavior where createOperation uses
the key's stored algorithm rather than requiring it in op params
Stub addition:
- ServiceSpecificException compile stub (framework-internal class
resolved at runtime on device)
Tested on OnePlus Android 16 (SDK 36), KeyDetector passes all three
probes: updateAad succeeds, TOO_MUCH_DATA returns code=21,
INVALID_OPERATION_HANDLE returns after abort.
PADDING (tag 6) is ENUM_REP in Tag.aidl, meaning SET OF INTEGER in the
attestation extension ASN.1, same as PURPOSE and DIGEST. Commit 1fa6f5a
added it as individual [6] INTEGER entries, causing parsers to fail with
CertificateParsingException on any RSA key attestation.
Fork identity: rename across module metadata, CI pipeline, and build
scripts. Version scheme changed from v4.5-115-f388529 to v4.6-117
format, commit count auto-increments, git hash dropped from filenames.
The old Gaussian sleep (mean=55ms, stddev=12ms) triggered detection on
Chunqiu Native Check 2.8. A flat 15ms floor satisfies the minimum RTT
threshold without creating a detectable delay pattern, both attested
and non-attested paths get identical treatment, keeping the D50 ratio
at ~1.0 while staying above the >=15ms requirement.
signerAlgorithm was derived from params.algorithm (the generated key)
instead of the signing key, causing BouncyCastle to throw when signing
RSA keys with an EC attestation key. Now reads signingKeyPair.private.algorithm.
Device ID tags (serial/imei/meid/secondImei) were blanket-rejected
instead of flowing through to software cert gen like AOSP does.
Narrowed rejection to DEVICE_UNIQUE_ATTESTATION only.
toAuthorizations() was missing OS_VERSION, OS_PATCHLEVEL, VENDOR_PATCHLEVEL,
BOOT_PATCHLEVEL, CREATION_DATETIME, USER_ID, PADDING, and RSA_PUBLIC_EXPONENT
tags that real TEE-generated KeyMetadata always includes. EC_CURVE was also
hardcoded unconditionally, producing invalid authorizations for RSA keys.
Additionally, live-patched certificate chains in getKeyEntry weren't cached,
causing re-patching on every call with potentially different signatures.
Ports upstream JingMatrix/TEESimulator#148 and #150.
The deletion guard must not shadow re-generated keys. If an app
deletes a key then re-creates it, getKeyEntry was still returning
KEY_NOT_FOUND because deletedSoftwareKeys was checked first.
After deleting a software-generated key, getKeyEntry was falling
through to the real keystore2 service which could return a stale
hardware key with the same alias. The post-transact live-patch
fallback would then resurrect the key with a patched chain,
detectors flag this as binder inconsistency.
Track deleted software key aliases and return KEY_NOT_FOUND (7) for
subsequent getKeyEntry calls. Also always invoke cleanupKeyData on
delete to clear stale patchedChains entries for hardware keys.
Software-generated keys complete in ~4ms, real TEE averages 55-65ms
with a floor around 15ms. Detectors measure this RTT to distinguish
software from hardware paths. Gaussian delay sampling (mean=55ms,
σ=12ms, floor=15ms) brings total RTT into the expected range.
Cherry-pick three upstream fixes: Parcel position reset in hasException()
so the method doesn't consume reply data (bab7093), list_past_alias
enumeration filter inversion (71f75de), and KeyMetadata alignment with
AOSP semantics, modificationTimeMs, Tag.ORIGIN, KeyDescriptor
normalization (4e3dcc5).
Additionally, createErrorReply() was missing the empty remote stack
trace header int between the exception message and error code, per
AOSP Status.cpp:196. Binder readers expecting the standard
EX_SERVICE_SPECIFIC wire format would misparse our error replies.
paths-ignore for .github/** was preventing workflow-only pushes
from triggering the pipeline at all. Release job was gated to
push events only, so workflow_dispatch never published. Simplify
paths-ignore to just **.md and allow both push and dispatch to
trigger the release job.
Supervisor had zero-delay restart on crash loops, pins CPU core at
100% if daemon keeps dying. Add exponential backoff (500ms to 30s cap,
resets after 30s stable). Set nice=10 on daemon child to yield CPU
to foreground apps. Evict stale entries from fileLocks and rate limiter
ConcurrentHashMaps that grew unbounded. Upload pre-built flashable zips
in CI instead of unpacking and re-compressing loose files.
debug() was hitting Log.d() unconditionally in release builds,
every intercepted binder transaction triggered string formatting
and logcat syscalls. verbose() already had the guard; debug() was
just missing it. Also remove dead SERVICE_SLEEP_MS constant.
The workflow only uploaded unzipped contents as CI artifacts,
no GitHub release was ever created from CI. Restructured into
build + release jobs: build produces both debug and release ZIPs
(renamed to clean `TEESimulator-vX.Y-{Variant}.zip` format),
release extracts changelog from module/changelog.md and publishes
a GitHub release with both ZIPs attached.
Gradle's buildRustCertgen task requires cargo-ndk and Android NDK
targets to cross-compile libcertgen.so. Without these, CI fails on
any commit after 6aba82e which wired the Rust crate into the pipeline.
AIDL methods use codes 1..0x00ffffff. System transactions like
PING_TRANSACTION (0x5f4e4750) fall above that range. Intercepting
pings forces a full JNI round-trip to Java and back, adding enough
latency for timing detectors to flag the ratio (3.85x vs 3.0x
threshold). Early-return for codes above LAST_CALL_TRANSACTION
eliminates this overhead while preserving all AIDL interception.
Leaf cert Subject CN used "KeyStore" (capital S) but AOSP
KeyGenParameterSpec uses "Keystore" (lowercase s). Fixed in both
the Rust native certgen and BouncyCastle paths.
Replicate keystore2's security_level.rs parameter validation for
software-generated keys: reject CREATION_DATETIME (output-only tag,
ResponseCode 20) and device ID attestation tags (CANNOT_ATTEST_IDS
-66) that real keystore2 blocks before they reach the HAL.
Also fix createErrorReply parcel write order, AIDL protocol expects
exception_code, message, error_code but we had message and error_code
swapped, causing malformed replies for positive error codes.
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.