-
Notifications
You must be signed in to change notification settings - Fork 1
fix: prevent keychain persistence on app uninstall #299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements encryption for keychain data to prevent persistence after app uninstall. Since iOS keeps keychain data by default after uninstall, the solution encrypts all keychain entries with a key stored in the App Group directory (which is deleted on uninstall). The app also detects and wipes orphaned keychain data on fresh installs when the encryption key is missing.
Key Changes:
- Added
KeychainCryptoutility class with AES-GCM encryption/decryption for keychain data - Modified keychain accessibility from
AfterFirstUnlocktoAfterFirstUnlockThisDeviceOnlyto prevent iCloud sync - Implemented orphaned keychain detection and cleanup on app launch
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| Bitkit/Utilities/KeychainCrypto.swift | New encryption utility providing AES-GCM encryption/decryption with key management |
| Bitkit/Utilities/Keychain.swift | Integrated encryption/decryption into save/load operations and updated accessibility attribute |
| Bitkit/Utilities/Errors.swift | Added failedToDecrypt error case for decryption failures |
| Bitkit/Utilities/AppReset.swift | Added encryption key deletion and App Group UserDefaults cleanup to wipe operation |
| Bitkit/AppScene.swift | Added orphaned keychain detection and cleanup on app launch |
| BitkitTests/KeychainCryptoTests.swift | Comprehensive test suite for encryption/decryption functionality |
| BitkitTests/KeychainTests.swift | Added encryption integration tests and updated existing tests |
| BitkitTests/KeychainiCloudSyncTests.swift | New test suite verifying keychain items don't sync to iCloud |
| Bitkit.xcodeproj/project.pbxproj | Added KeychainCrypto.swift to project build configuration |
Description
The default Keychain behavior is keep the data after uninstall and there is no flag to change it. This was causing the seed and other sensitive data being persisted and recovered on app re-install.
The solution is encrypt Keychain data with a key that is deleted on app uninstall, making the persisted keychain data useless.
Also, on fresh install, the app checks for orphaned keychain data and wipes it
Manual testing scenarios
1. Fresh Install Flow
Action: Delete app, reinstall, create new wallet
Expected logs:
"Created new encryption key" - KeychainCrypto"Saved encryption key to <path>/.keychain_encryption_key" - KeychainCrypto"Encrypted data (X bytes → Y bytes)" - KeychainCrypto(for mnemonic save)"Saved bip39_mnemonic_0" - Keychain"bip39_mnemonic_0 loaded and decrypted from keychain" - Keychain(when loaded)Verification:
Verify encryption key file created at
<App Group>/<network>/.keychain_encryption_keyVerify keychain items are encrypted (not plaintext in raw keychain query)
Verify wallet functions normally
2. Restore Wallet Flow
Action: Delete app, reinstall, restore from mnemonic
Expected logs:
"Created new encryption key" - KeychainCrypto"Encrypted data (X bytes → Y bytes)" - KeychainCrypto(for each item)"Saved bip39_mnemonic_0" - Keychain"Saved bip39_passphrase_0" - Keychain(if passphrase used)"bip39_mnemonic_0 loaded and decrypted from keychain" - KeychainVerification:
Verify encryption key file created
Verify restored data encrypted in keychain
Verify wallet functions normally
3. App Wipe Flow
Action: Go to Settings → Backup or restore → Reset and Restore -> Reset Wallet
Expected logs:
"Deleted bip39_mnemonic_0 from keychain" - Keychain"Deleted bip39_passphrase_0 from keychain" - Keychain(if exists)"Deleted security_pin from keychain" - Keychain(if exists)"Deleted encryption key" - KeychainCrypto"Wiped App Group UserDefaults" - AppResetVerification:
Verify encryption key file deleted
Verify keychain wiped (no bip39 items)
Verify App Group UserDefaults wiped
Verify onboarding shown
4. Simulated Uninstall/Reinstall (Orphaned Keychain)
<App Group>/<network>/.keychain_encryption_key)Expected logs:
"Detected orphaned keychain state - keychain exists but encryption key missing. Forcing fresh start." - AppScene(WARN level)"Deleted bip39_mnemonic_0 from keychain" - Keychain"Deleted bip39_passphrase_0 from keychain" - Keychain(if exists)"Orphaned keychain wiped. App will show onboarding." - AppSceneVerification:
Verify orphaned keychain detected and wiped
Verify onboarding shown (fresh start)
5. Existing User Migration (Master → Encryption Branch)
Expected logs on first launch:
"Detected legacy unencrypted keychain - migration will proceed normally" - AppScene(INFO level)"bip39_mnemonic_0 appears to be legacy unencrypted data, returning as-is" - Keychain(WARN level)No encryption key creation yet (key created on first save)
Intermediate Verification:
Verify wallet still accessible (mnemonic loads successfully)
Action: Create new keychain entry (e.g., set PIN or add passphrase)
Expected logs on first save:
"Created new encryption key" - KeychainCrypto"Saved encryption key to <path>/.keychain_encryption_key" - KeychainCrypto"Encrypted data (X bytes → Y bytes)" - KeychainCrypto"Saved security_pin" - Keychain(or whichever item was saved)Final Verification:
Verify encryption key created after first modification
Verify new entry is encrypted
On next app launch: Old mnemonic still loads with WARN log (still plaintext); New PIN loads with decrypt log (now encrypted)
6. Migration with Passphrase
Expected logs:
"Detected legacy unencrypted keychain - migration will proceed normally" - AppScene"bip39_mnemonic_0 appears to be legacy unencrypted data, returning as-is" - Keychain(for 24-word mnemonic)"bip39_passphrase_0 appears to be legacy unencrypted data, returning as-is" - Keychain(for passphrase)Verification:
Verify 24-word mnemonic loads correctly (using
validateMnemonic()validation)Verify passphrase loads correctly
Action: Modify any keychain item (e.g., set PIN)
Expected logs:
"Created new encryption key" - KeychainCrypto"Encrypted data" - KeychainCrypto(for new item)Final Verification:
Verify wallet still functions with 24-word mnemonic + passphrase
Linked Issues/Tasks
Closes #293
Screenshot / Video
restore.mp4
unninstall-reinstall.mp4
migration.mp4
migration-with-passphrase.mp4