Skip to content

gmscompat: add scoped recoverable keystore permission + recovery keystore cleanup on GmsCore data clears - #428

Merged
thestinger merged 9 commits into
GrapheneOS:17from
inthewaves:gms-recoverykeystore
Sep 13, 2026
Merged

gmscompat: add scoped recoverable keystore permission + recovery keystore cleanup on GmsCore data clears#428
thestinger merged 9 commits into
GrapheneOS:17from
inthewaves:gms-recoverykeystore

Conversation

@inthewaves

Copy link
Copy Markdown
Member

No description provided.

Let the GmsCompat package flag grant RECOVER_KEYSTORE_GMSCORE to Play services and accept it only
for the account keychain RecoveryController methods used for setup, sync, generated keys, status,
snapshots and restore sessions.

We keep remaining functions like direct raw key import and remote lockscreen validation behind the
existing privileged permissions.
}
}

static void onApplicationDataCleared(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GosPackageState infrastructure changes should be in a separate gosps: tagged commit to simplify future squashing of history.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hook should be removed in favor of the new onApplicationDataCleared() hook.

}
}

static boolean shouldClearGmsCoreRecoverableKeystoreState(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method name is misleading and it's used by non-GosPackageState code. Can move it to GmsCoreHooks class or create a new class. Same applies to clearGmsCoreRecoverableKeystoreState() below.


String className = component.getClassName();
String action = intent.getAction();
return (FIDO_REGISTRATION_ACTIVITY.equals(className)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why check class name in addition to checking the intent action? Intent action is part of the API, class name is not.


static void onApplicationDataCleared(
PackageManagerService pm, String packageName, int userId) {
PackageStateInternal packageState =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call is needed only when packageName is GmsCore.

RecoverableKeyStore identifies recovery agent state by Android user ID and recovery agent UID. It
removes that state when an Android user is removed, but has no equivalent cleanup for app data
removal. On the stock OS, Package Manager restores GmsCore's saved app ID when updates are
uninstalled, so it normally keeps the same UID. On GrapheneOS, sandboxed GmsCore can be fully
uninstalled; a later installation is a fresh install and may receive a new UID. Also, clearing its
storage keeps the UID but discards the app state associated with its recoverable keystore entries.

After successful app data removal for GmsCore installed as a user app, remove its database rows,
snapshot, listener and pending snapshot state, recovery sessions and application keys. Preserve
system apps and DELETE_KEEP_DATA flows such as package replacement and archiving.

Delete agent-owned rows by user ID and UID using each table's schema contract and one transaction.
Enumerate the internal alias prefix so restored application keys without database rows are included,
and serialize cleanup with pending key sync work. Keep the per-user platform keys because they are
shared by all recovery agents for the user.
Verify LockSettings serializes cleanup with key sync work and removes sessions, listeners,
snapshots, internal Android Keystore aliases and only the matching recovery agent database rows.

Keep regression coverage for the existing removed Android user cleanup path, including its
database-backed key enumeration and user-wide database removal.

Test: atest FrameworksServicesTests:ApplicationKeyStorageTest
Test: atest FrameworksServicesTests:RecoverableKeyStoreManagerTest
Test: atest FrameworksServicesTests:RecoverySnapshotListenersStorageTest
Test: atest FrameworksServicesTests:CleanupManagerTest
Test: atest FrameworksServicesTests:RecoverableKeyStoreDbTest
… Find Hub

When RECOVER_KEYSTORE_GMSCORE is not granted, ask GmsCompat to show a notification linking to
its recovery keystore permission screen.

Require a visible com.google AccountManager account before notifying during passkey use. The same
FIDO activities are used during initial Google account sign-in, before Play services has an
account whose account keychain could use the permission. If the account query fails, skip the
optional notification.
Derive the LSKF hash immediately before it is used to protect the recovery key and zeroize it
afterward instead of retaining it while recoverable keys are loaded and encrypted.

Destroy secrets when an explicitly closed recovery session is removed. Zeroization clears only
the current movable array and cannot erase copies made by the runtime. Trailing underscores mark
owned locals that must not be used after zeroization.

After key sync enters the cryptographic path, run best effort garbage collection once the task
releases its credential to shorten retention of unreachable Java objects and associated native
contexts. This does not guarantee that stale heap copies or native temporary data are overwritten.

Test: atest FrameworksServicesTests:KeySyncTaskTest
Test: atest FrameworksServicesTests:RecoverySessionStorageTest
Allow GmsCompat to inform users if Bluetooth auto-off is on, since it might affect Find Hub
functionality that they might want.
@inthewaves

Copy link
Copy Markdown
Member Author

@thestinger
thestinger merged commit 1ebfb71 into GrapheneOS:17 Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants