JingMatrix / JingMatrix/TEESimulator

Canary 67: expired EC attestation intermediate in keybox causes Play Integrity to become UNEVALUATED in patch mode

Open
#293 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
2.4k
Forks
312
Avg merge
2d 8h
Merged PRs (30d)
1

Description

Summary

On a Pixel 8 Pro (husky) running GrapheneOS / Android 17, enabling TEESimulator v4 Canary 67 changes Play Integrity from a working MEETS_BASIC_INTEGRITY result to a completely UNEVALUATED response.

I isolated the behavior to TEESimulator:

  • Play Integrity Fix is disabled during reproduction.
  • TrickyStore is installed but disabled.
  • Without TEESimulator, Play Integrity returns MEETS_BASIC_INTEGRITY.
  • With TEESimulator enabled, the complete Play Integrity evaluation becomes UNEVALUATED.

The configured keybox contains an expired EC attestation intermediate certificate.

TEESimulator nevertheless uses that EC chain in patch mode and reports:

patch_attestation -> 3 cert(s) [patched leaf + keybox EC chain]

OpenSSL independently confirms that the EC intermediate certificate expired on January 8, 2026.


Environment

  • Device: Google Pixel 8 Pro (husky)
  • OS: GrapheneOS
  • Android: 17
  • SDK: 37
  • Root: Magisk
  • Zygisk implementation: NeoZygisk
  • TEESimulator: v4.0 (67-4f42350-debug)
  • versionCode: 67
  • commit: 4f42350
  • TEESimulator mode: patch
  • TrickyStore: installed but disabled
  • Play Integrity Fix: disabled during reproduction
  • Secure App Spawning for Google Play Services: disabled
  • com.google.android.gms.unstable is confirmed to be spawned from zygote64

TEESimulator module information:

id=teesim
name=TEESimulator
version=v4.0 (67-4f42350-debug)
versionCode=67
author=JingMatrix

TEESimulator configuration

{
  "version": 1,
  "profiles": {
    "default": {
      "keybox": "keybox.xml",
      "mode": "patch",
      "patchLevel": {
        "system": "today",
        "vendor": "YYYY-MM-05",
        "boot": "YYYY-MM-05"
      },
      "osVersion": "",
      "brand": "",
      "device": "",
      "product": "",
      "manufacturer": "",
      "model": "",
      "serial": "",
      "imei": "",
      "meid": "",
      "imei2": "",
      "apps": [
        "com.google.android.gms",
        "com.android.vending",
        "io.github.vvb2060.keyattestation"
      ]
    }
  }
}

Reproduction

  1. Disable Play Integrity Fix.
  2. Disable TEESimulator.
  3. Reboot.
  4. Run a Play Integrity test.
  5. BASIC integrity is returned normally.
  6. Enable TEESimulator with the configuration shown above.
  7. Reboot.
  8. Run the same Play Integrity test again.
  9. The complete Play Integrity evaluation becomes UNEVALUATED.

This behavior is reproducible.


Result with TEESimulator disabled

The same Play Integrity checker receives:

{
  "requestDetails": {
    "requestPackageName": "gr.nikolasspyr.integritycheck"
  },
  "appIntegrity": {
    "appRecognitionVerdict": "PLAY_RECOGNIZED"
  },
  "deviceIntegrity": {
    "deviceRecognitionVerdict": [
      "MEETS_BASIC_INTEGRITY"
    ],
    "recentDeviceActivity": {
      "deviceActivityLevel": "UNEVALUATED"
    },
    "deviceAttributes": {
      "sdkVersion": 37
    }
  },
  "accountDetails": {
    "appLicensingVerdict": "LICENSED"
  },
  "environmentDetails": {
    "playProtectVerdict": "POSSIBLE_RISK"
  }
}

The important part is:

MEETS_BASIC_INTEGRITY

Result with only TEESimulator enabled

{
  "requestDetails": {
    "requestPackageName": "gr.nikolasspyr.integritycheck"
  },
  "appIntegrity": {
    "appRecognitionVerdict": "UNEVALUATED"
  },
  "deviceIntegrity": {
    "recentDeviceActivity": {
      "deviceActivityLevel": "UNEVALUATED"
    },
    "deviceAttributes": {}
  },
  "accountDetails": {
    "appLicensingVerdict": "UNEVALUATED"
  },
  "environmentDetails": {
    "playProtectVerdict": "UNEVALUATED",
    "appAccessRiskVerdict": {}
  }
}

There is no BASIC / DEVICE / STRONG verdict at all.

Even appRecognitionVerdict, licensing and Play Protect change to UNEVALUATED.


Relevant TEESimulator log

During the failing Play Integrity request TEESimulator logs:

RKP: denying TE IRemoteProvisioning transact code=1 for target uid=10147
(irpcName=android.hardware.security.keymint.IRemotelyProvisionedComponent/default,
remote_provisioning.tee.rkp_only=<unset>;
keystore2 will append no real attest-key chain;
our generation stays keybox-rooted)

keystore2:
Failed to get rkpd key
Status(-129, EX_TRANSACTION_FAILED): 'FAILED_TRANSACTION: '

patch: real HAL returned 4 cert(s);
re-signing only the leaf under the keybox

patch_attestation applying profile:
os_version=170000
os_patchlevel=202609
vendor_patchlevel=20260905
boot_patchlevel=20260905
ids=profile

patch_attestation -> 3 cert(s) [patched leaf + keybox EC chain]

patch: emitted 3-cert chain
(real key blob kept, leaf re-rooted at keybox)

This suggests that the real hardware attestation itself is working:

real HAL returned 4 cert(s)

The failure appears after TEESimulator re-signs / re-roots the attestation using the configured EC keybox chain.


Keybox certificate validation

I extracted only the public certificates from the configured keybox.

No private key material is included below.

EC intermediate
subject=
C=US,
ST=California,
O=Google, Inc.,
OU=Android,
CN=Android Keystore Software Attestation Intermediate

issuer=
C=US,
ST=California,
L=Mountain View,
O=Google, Inc.,
OU=Android,
CN=Android Keystore Software Attestation Root

serial=1001

notBefore=Jan 11 00:46:09 2016 GMT
notAfter=Jan 8 00:46:09 2026 GMT

sha256 Fingerprint=
B9:8B:2D:68:7C:24:43:4B:0F:FA:20:EB:88:9E:18:0F:
E2:D5:14:88:3D:69:54:84:EB:B4:FF:83:A6:63:9B:9B

Public-Key: (256 bit)

The intermediate certificate is expired on the current date.

EC root
subject=
C=US,
ST=California,
L=Mountain View,
O=Google, Inc.,
OU=Android,
CN=Android Keystore Software Attestation Root

issuer=
C=US,
ST=California,
L=Mountain View,
O=Google, Inc.,
OU=Android,
CN=Android Keystore Software Attestation Root

serial=A2059ED10E435B57

notBefore=Jan 11 00:43:50 2016 GMT
notAfter=Jan 6 00:43:50 2036 GMT

sha256 Fingerprint=
51:D4:96:AD:46:64:19:0F:BD:AF:1A:D9:87:27:8E:FA:
35:D6:FB:F5:2E:50:A9:C3:4D:ED:83:95:47:7E:DE:5C

Public-Key: (256 bit)

The root itself is still valid.


OpenSSL verification

Verification of the EC intermediate against the EC root:

C=US, ST=California, O=Google, Inc., OU=Android,
CN=Android Keystore Software Attestation Intermediate

error 10 at 0 depth lookup: certificate has expired
error cert-1.pem: verification failed

So the EC certificate chain used by TEESimulator fails normal X.509 validity checking because the intermediate expired on:

2026-01-08

Key types present in the keybox

cert-1.pem: Public-Key: (256 bit)
cert-2.pem: Public-Key: (256 bit)
cert-3.pem: Public-Key: (1024 bit)
cert-4.pem: Public-Key: (1024 bit)

The two 256-bit certificates are the EC intermediate/root shown above.

TEESimulator explicitly reports that it emits:

[patched leaf + keybox EC chain]

so the expired EC intermediate appears to be part of the chain actually used for the patched attestation.


RKP / KeyMint availability

The device itself exposes the normal KeyMint and Remote Key Provisioning services:

android.hardware.security.keymint.IKeyMintDevice/default
android.hardware.security.keymint.IKeyMintDevice/strongbox
android.hardware.security.keymint.IRemotelyProvisionedComponent/default
android.hardware.security.keymint.IRemotelyProvisionedComponent/strongbox
android.security.rkp.IRemoteProvisioning

Relevant properties:

remote_provisioning.hostname=remoteprovisioning.googleapis.com

ro.product.brand_for_attestation=google
ro.product.device_for_attestation=husky
ro.product.manufacturer_for_attestation=Google
ro.product.model_for_attestation=Pixel 8 Pro

ro.strongbox.manufacturer=Google
ro.strongbox.model=Titan-M2

Therefore this does not appear to be a device where RKP is simply unavailable.


Difference from issue #292

I also checked issue #292.

My case appears to be different.

In #292, the affected device already fails its native attestation path / KeyMaster operation and does not expose:

android.hardware.security.keymint.IRemotelyProvisionedComponent/default

In my case:

  • the Pixel exposes both TEE and StrongBox RKP components,
  • the real HAL successfully returns a 4-certificate attestation chain,
  • TEESimulator patch mode successfully receives that chain,
  • the failure appears after the leaf is re-rooted using the configured EC keybox chain,
  • that EC chain contains an intermediate certificate that is already expired.

Therefore I don't think this is the same "broken TEE" situation discussed in #292.


Additional observations

TrickyStore is installed but disabled:

/data/adb/modules/tricky_store/disable

The TrickyStore and TEESimulator keybox files happen to be byte-identical:

57ff10ee68b76aecf7760da9bb3bba621d8509d8196e10a22d89aa8371cbb849  /data/adb/teesim/keybox.xml
57ff10ee68b76aecf7760da9bb3bba621d8509d8196e10a22d89aa8371cbb849  /data/adb/tricky_store/keybox.xml

However, because TrickyStore is disabled, it should not be participating in the failing test.

Secure App Spawning for Google Play Services is also disabled.

The GMS unstable process is confirmed to be spawned through Zygote:

u0_a147  ...  com.google.android.gms.unstable
PPid: zygote64

This also appears different from the GrapheneOS Secure App Spawning issue discussed in #261.


Expected behavior

If a certificate in a configured keybox signing chain is expired, I would expect TEESimulator to do one of the following:

  1. Reject the keybox/profile as invalid.
  2. Display a clear warning in the WebUI.
  3. Log that one of the certificates in the selected chain has expired.
  4. Avoid replacing an otherwise working hardware attestation with an already-invalid certificate chain.

Currently the operation is reported as successful:

patch_attestation -> 3 cert(s) [patched leaf + keybox EC chain]

even though the resulting EC chain fails ordinary X.509 validity checking.


Questions

  1. Does TEESimulator currently validate notBefore / notAfter for every certificate in the selected keybox chain?

  2. Should an expired intermediate certificate make the keybox or profile invalid?

  3. Could the WebUI display certificate validity dates and an expired / valid status when importing or selecting a keybox?

  4. Is the EC chain selected automatically in patch mode for this type of request?

  5. Is there any fallback behavior if the EC chain is expired but another certificate chain is present in the keybox?

  6. Could the expired EC intermediate explain why Play Integrity changes from MEETS_BASIC_INTEGRITY to a completely UNEVALUATED response?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the failure in TEESimulator patch mode with the supplied keybox.xml and inspect the patch_attestation logs. Use OpenSSL to confirm the EC intermediate's validity dates and trace where the selected certificate chain is accepted. Done means establishing whether expired chains are validated and documenting or implementing the expected rejection, warning, or fallback behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
cryptography, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.