How can I test CloudKit User Keychain Reset?

My question

  • Is there a way to perform an iCloud keychain reset in order to be able to test CKErrorUserDidResetEncryptedDataKey ?

I found this section in the CloudKit documentation https://developer.apple.com/documentation/cloudkit/encrypting-user-data#Handle-a-User-Keychain-Reset

I want to be prepared for the zoneNotFound / CKErrorUserDidResetEncryptedDataKey case.

However, I can't find a way to actually reproduce this error with an iCloud (test-) user and can't find any Apple documentation on how to perform sucha "User Keychain Reset".

The only thing that almost looked like it I came across was in the Keychain.app's Settings "Reset Default Keychains…". However, performing this didn't seem to affect the CloudKit data used in our App at all.

I've been trying to do this with an Apple account that has 2FA active and a recovery account assigned.

We're only targetting >= iOS 18, macOS >= 15.

I don't have a testing environment to try for now, but am wondering if you restart the machine after resetting keychains, and also how you verify that the reset doesn't impact the CloudKit data access. Would you mind to share the detailed steps you use for your testing?

Also, did you try to delete the keychains, as described in Delete a keychain in Keychain Access on Mac?

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

The app I'm working on

  • creates a single zone (e.g. CKRecordZone(zoneName: "Documents") in the private database
  • syncs documents as CKRecord to that zone, which have some fields set via encryptedValues[…]
  • When launching the app after clearing local caches, the app fetches all zones and fetches all records from the "Documents" zone if it exists already.
  • in my understanding, the data in CloudKit is encrypted with a key that should be resettable in some way and then would become unreachable afterwards

My setup

  • I tried this with a debug build (icloud development env) and a testflight release (icloud production env) on a separate user on my mac, which was logged in to its own iCloud account.

What did I try?

  • I closed my app, cleared its caches and then
  • ran "Reset Default Keychains…" in the Settings of the Keychain Access app to attempt resetting encrypted data.
  • I relaunched my app

Expected result

  • I expected the "Documents" zone to be gone, and no documents to reappear, technically expecting zoneNotFound+ CKErrorUserDidResetEncryptedDataKey

Actual result

  • All documents were redownloaded from the "Documents" zone. This means the zone still existed with all data accessible.
  • Also, the data was still there when I checked in the CloudKit Console (development and production environment)

I had a look at Delete a keychain in Keychain Access on Mac but I think it doesn't apply because "Delete Keychain iCloud…" is greyed out.

Thanks for sharing the details.

What did I try? I closed my app, cleared its caches and then ran "Reset Default Keychains…" in the Settings of the Keychain Access app to attempt resetting encrypted data. I relaunched my app

Did you try to restart your machine before relaunching your app to see if that changes anything?

I had a look at Delete a keychain in Keychain Access on Mac but I think it doesn't apply because "Delete Keychain iCloud…" is greyed out.

Indeed. Since you have the testing environment, would you mind to try the following?

  1. Launch Settings, go to Settings > Your Apple ID > iCloud > Passwords (Under the Saved to iCloud section), and uncheck the Sync this Mac box. (If you are on a system earlier than macOS 15, the setting may be in a different place.)

  2. Launch Keychain Access.app (or relaunch if you've launched it) and observe that the iCloud keychain (under the Default Keychains section) disappears. You can then quit Keychain Access.app.

  3. Find the login keychain file (~/Library/Keychains/login.keychain-db) and remove it. Be sure that the file is deleted, rather than being moved to the trash bin.

  4. Restart your Mac and try your app to see if there is any difference.

I can't try step 3 on my machine, and so need to rely on you to see what happens. Sorry for that.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Given your questions and suggestions I tried the following steps now

  • Launch Setting’s and go to Settings -> Apple Account -> iCloud -> See All -> iCloud Passwords & Keychain
  • Click the “Sync this Mac” toggle to disable it
  • Actual result: Nothing happens, the toggle remains active
  • Log out the mac user and log back in in the toggle has actually been updated to look disabled
  • The iCloud Keychain is now gone from the Keychain Access app.
  • ❎ The CloudKit app data is still available (All documents were redownloaded from the "Documents" zone. This means the zone still existed with all data accessible.)
  • Fully delete the login keychain at ~/Library/Keychains/login.keychain-db
  • Restart the mac
  • ❎ The CloudKit app data is still available
  • Reactivate Settings / iCloud Passwords & Keychain / “Sync this Mac”
  • The iCloud Keychain is back to the Keychain Access app
  • Keychain App -> Settings -> Reset Default Keychains…
  • Restart the Mac
  • A system alert shows “This Mac can’t connect to iCloud because of a problem with “<email>”, Open Apple Account settings to fix this problem.
  • App shows iCloud as temporarily unavailable and can’t sync
  • Settings app asks for Apple Account password, I enter it
  • App syncs again
  • ❎ The CloudKit app data is still available

It seems that none of these steps reset the key(s) used for encrypting data in CloudKit.

Do you have any other ideas or could you perhaps reach out to find an answer to the question of how the user keychain reset can be triggered?

The iCloud Keychain is now gone from the Keychain Access app. ❎ The CloudKit app data is still available (All documents were redownloaded from the "Documents" zone. This means the zone still existed with all data accessible.) Fully delete the login keychain at ~/Library/Keychains/login.keychain-db Restart the mac ❎ The CloudKit app data is still available

Thanks for your experiments. Given that the iCloud and login keychains are both gone, the above result is a surprise to me...

I'd see if my colleague who focuses on keychain has something to say. At the same time, if you don't mind, please file a feedback report with all the information here and share the report ID, which I would use for our internal communication.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thanks for reaching out to your colleague(s). Here is the report ID: FB16366133

My CloudKit friend reminded me the following:

  • You need to turn on iCloud Advanced Data Protection (Settings > Apple ID > iCloud > Advanced Data Protection) for the testing. With ADP being on, a deleted key is un-recoverable, and triggers the zone deletion on the server side.

  • The zone deletion is asynchronous, and so it may take some time after ADP is on and the keychain is reset.

So would you mind to check if ADP is on on your testing device, and if it is off, turn it on and try with another test?

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

I've tested with ADP on, and it didn't change the result. The Zone is still available. Could it have anything to do with the zone being a custom zone, and also having at least one record which is using CKShare with hierarchical sharing?

I don't believe so. Did you ever test with the default zone in your private database? I think that will give you the same result.

I still have the feeling though, that just clearing the keychain using Keychain Access app don't really reset the CloudKit encryption key.

I have actually checked with my keychain and security friend and he didn't see anything wrong from the keychain side.

I guess I'll need to set up and test with my own environment before I can say more. Since you've filed the feedback report, I guess you can update it with the result of your latest try, and see if the CloudKit team has any comment.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

I guess I'll need to set up and test with my own environment before I can say more. Since you've filed the feedback report, I guess you can update it with the result of your latest try, and see if the CloudKit team has any comment.

I appreciate that! Let me know if you can or can't find a way to make it happen.

I can't help with the trigger, and I want that out of the way first — I've never reproduced a keychain reset deliberately either, and this thread is already further along that than I got. The ADP precondition above is new to me.

But there's something worth knowing before anyone tries this in the default zone, as suggested above, because there it decides whether the test can tell you anything. The documented handling in https://developer.apple.com/documentation/cloudkit/encrypting-user-data is three steps, and the first is "Delete the relevant zones." If the affected records are in the default zone of the private database, that step can't execute — the default zone is the catch-all for every record that didn't name a zone, so deleting it is either refused or far broader than the recovery you're attempting. You'd trigger the reset successfully, watch the recovery fail, and have every reason to think the trigger was still wrong.

I found that by accident rather than by looking for it. Two unrelated-looking errors, both only visible against a real container on a device, both the default zone:

Field 'recordName' is not marked queryable
AppDefaultZone does not support getChanges call

The first is the one with a thousand search results, all saying to mark recordName queryable in CloudKit Console. That works, in development — schema is per-environment, and the docs say plainly to "Promote this schema change to your production environment before deploying any app changes to the App Store that rely on the new schema." So it converts a code problem into a release-checklist problem. The second is what you hit if you switch to recordZoneChanges to avoid the index entirely, and it is documented in an easy place to miss: CKFetchRecordZoneChangesOperation's own page doesn't say which zones support it, while CKRecordZone.default() does — in terms of CKFetchRecordChangesOperation, deprecated back in iOS 10. For the operation you'd actually write the only statement I can find is in the documentation archive, on a deprecated web-services endpoint: "Fetching record changes is supported only for custom zones."

Moving to a custom zone fixed all three at once, and the third is the reason it isn't a style preference — it's the only one of them that makes the recovery path above executable at all.

Two smaller things for anyone landing here from a search, which you've both got right already but the error strings are worth having in one place. CKErrorUserDidResetEncryptedDataKey is a userInfo key, not a CKError.Code case — the code is zoneNotFound, and checking for it as a case doesn't compile, which is lucky. And on the testing problem itself: constructing a CKContainer for an identifier the host has no matching entitlement for traps rather than throwing, so a unit test that touches one takes the process down instead of failing. Whatever harness you end up with for this probably can't be a plain unit test.

If anyone does find a reliable way to trigger the reset I'd like to know too — the recovery path is the one piece of this I can reason about but not exercise.

Full write-up, with the two errors and why the console fix for the first one is a trap: https://mcmizzle.com/blog/cloudkit-default-zone-limits/

How can I test CloudKit User Keychain Reset?
 
 
Q