Go to file
2026-02-18 14:59:13 -06:00
Config Add IterationWarningView 2026-01-19 09:00:15 -06:00
localPackages/SharedPackage removed extra string keychain samples locations 2026-01-21 08:27:35 -06:00
SecureStorageSample updated UserDefaults demo to not use AppVersionKey 2026-01-23 14:05:35 -06:00
SecureStorageSample Watch App Update docs: README, README 2026-01-19 09:00:15 -06:00
SecureStorageSample Watch AppTests Add ContentView, UserProfile, SecureStorageSample_Watch_AppApp, WatchConnectivityService (+13 more); Remove Value 2026-01-19 09:00:11 -06:00
SecureStorageSample Watch AppUITests Add ContentView, UserProfile, SecureStorageSample_Watch_AppApp, WatchConnectivityService (+13 more); Remove Value 2026-01-19 09:00:11 -06:00
SecureStorageSample.xcodeproj Signed-off-by: Matt Bruce <mbrucedogs@gmail.com> 2026-02-18 14:59:13 -06:00
SecureStorageSample.xcworkspace Add Package, StorageKeyNames, StorageServiceIdentifiers, UserProfile; Remove Package, Package, StorageKeyNames, StorageServiceIdentifiers (+1 more) 2026-01-19 09:00:14 -06:00
SecureStorageSampleTests Add SecureStorageSampleApp, SecureStorageSampleTests, SecureStorageSampleUITests, SecureStorageSampleUITestsLaunchTests; Remove SecureStorgageSampleApp, SecureStorgageSampleTests, SecureStorgageSampleUITests, SecureStorgageSampleUITestsLaunchTests 2026-01-19 09:00:13 -06:00
SecureStorageSampleUITests Add SecureStorageSampleApp, SecureStorageSampleTests, SecureStorageSampleUITests, SecureStorageSampleUITestsLaunchTests; Remove SecureStorgageSampleApp, SecureStorgageSampleTests, SecureStorgageSampleUITests, SecureStorgageSampleUITestsLaunchTests 2026-01-19 09:00:13 -06:00
.gitignore Add Logger 2026-01-19 09:00:14 -06:00
AGENTS.md Signed-off-by: Matt Bruce <mbrucedogs@gmail.com> 2026-02-18 14:59:13 -06:00
README.md Update docs: README 2026-01-19 09:00:15 -06:00
SecureStorageSample.code-workspace Signed-off-by: Matt Bruce <mbrucedogs@gmail.com> 2026-02-18 14:59:13 -06:00

SecureStorageSample

A sample iOS app demonstrating the LocalData package capabilities for secure, typed storage across multiple domains.

Features

This app provides interactive demos for all LocalData storage options:

Screen Demo Storage Domain
UserDefaults Save/load/remove values UserDefaults
Keychain Secure credentials with biometrics Keychain
File Storage User profiles with Codable models File System
Encrypted Storage Encrypted logs (AES or ChaCha20) Encrypted File System
Platform Sync Lab Platform availability & sync policies Multiple

The project also includes a watchOS companion app target for watch-specific demos. The watch app displays the synced user profile and the syncable setting from the Platform Sync Lab. On iPhone launch and when the watch becomes available, the app re-sends any syncable keys so the watch updates without manual re-entry. The watch app also requests a sync on launch when the iPhone is reachable.

Watch Sync Handshake

This sample uses a launch-order-safe handshake so either app can start first:

  1. Watch app launches → sends a request_sync message (or queues it if the iPhone is unreachable).
  2. iOS app receives the request → replies with a snapshot of current syncable keys and updates applicationContext.
  3. Watch app applies the snapshot → UI updates immediately.

This avoids requiring users to remember which app to open first.

Where the Logic Lives

  • iOS WCSession + handshake: SecureStorageSample/SecureStorageSample/Services/WatchConnectivityService.swift
  • Bootstrap on launch: SecureStorageSample/SecureStorageSample/SecureStorageSampleApp.swift
  • Sync policy UI lab: SecureStorageSample/SecureStorageSample/Views/PlatformSyncDemo.swift
  • Watch WCSession + request: SecureStorageSample/SecureStorageSample Watch App/Services/WatchConnectivityService.swift
  • Watch payload handlers: SecureStorageSample/SecureStorageSample Watch App/Services/Handlers/

Requirements

  • iOS 17.0+
  • watchOS 10.0+ (companion app target)
  • Xcode 15+

Getting Started

  1. Open SecureStorageSample.xcodeproj
  2. Select an iOS simulator or device
  3. Build and run (⌘R)
  4. To use App Group demos, enable the App Group entitlement for each target that should share data. The identifier is derived from the bundle ID via SharedKit constants.

Project Structure

SharedPackage/
├── Package.swift
└── Sources/
    └── SharedKit/
        ├── Constants/
        │   ├── StorageKeyNames.swift
        │   └── StorageServiceIdentifiers.swift
        └── Models/
            └── UserProfile.swift
SecureStorageSample/
├── ContentView.swift        # List-based navigation
├── Models/
│   ├── Credential.swift
│   └── SampleLocationData.swift
├── StorageKeys/
│   ├── UserDefaults/
│   ├── Keychain/
│   ├── FileSystem/
│   ├── EncryptedFileSystem/
│   ├── AppGroup/
│   └── Platform/
├── WatchOptimized.swift     # Watch data models
├── Services/
│   ├── AppStorageCatalog.swift
│   ├── ExternalKeyMaterialProvider.swift
│   └── WatchConnectivityService.swift
└── Views/
    ├── UserDefaultsDemo.swift
    ├── KeychainDemo.swift
    ├── FileSystemDemo.swift
    ├── EncryptedStorageDemo.swift
    └── PlatformSyncDemo.swift
SecureStorageSample Watch App/
├── SecureStorageSampleApp.swift
├── ContentView.swift
├── Protocols/
│   └── WatchDataHandling.swift
├── State/
│   └── WatchProfileStore.swift
└── Services/
    ├── WatchConnectivityService.swift
    └── Handlers/
        └── UserProfileWatchHandler.swift

Storage Design Philosophy

This app intentionally uses a Type-Safe Storage Design. Unlike standard iOS development which uses string keys (e.g., UserDefaults.standard.string(forKey: "user_name")), this library requires you to define StorageKey values.

Why types instead of strings?

  1. Safety: The compiler prevents typos. You can't accidentally load from "user_name" and save to "username".
  2. Codable Support: Keys define their own value types. You can store complex Codable structs or classes just as easily as strings, and the library handles the JSON/Plist serialization automatically.
  3. Visibility: All data your app stores is discoverable in the StorageKeys/ folder. It serves as a manifest of your app's persistence layer.
  4. Migration: You can move a piece of data from UserDefaults to EncryptedFileSystem just by changing the domain in the Key definition. No UI code needs to change.

Storage Key Examples

The app demonstrates various storage configurations:

UserDefaults

  • Simple string storage with automatic sync
  • App Group UserDefaults support for shared preferences

Keychain

  • 7 accessibility options (whenUnlocked, afterFirstUnlock, etc.)
  • 6 access control options (biometry, passcode, etc.)

File System

  • Documents: Permanent storage, backed up to iCloud. Use for critical user data.
  • Caches: Purgeable storage (iOS may delete when low on space), not backed up. Use for temporary data.
  • JSON and PropertyList serializers supported.

App Group Storage

  • Shared UserDefaults via App Group identifier
  • Shared files in the App Group container
  • Requires App Group entitlements in all participating targets

Encrypted Storage

  • AES-256-GCM or ChaCha20-Poly1305 encryption
  • PBKDF2 or HKDF key derivation
  • PBKDF2 iteration count must remain consistent or existing data will not decrypt
  • Complete file protection
  • External key material example via KeyMaterialProviding
  • Global encryption configuration (Keychain service/account) in app init

Platform & Sync

  • Platform availability (phoneOnly, watchOnly, all)
  • Sync policies (never, manual, automaticSmall)
  • Global sync configuration (max file size) in app init

Data Migration

  • Fallback: Automatically moves data from legacyMigrationSource to modernMigrationDestination on first access using protocol-based migration.
  • Transforming: Converts a legacy full-name string into a structured ProfileName.
  • Aggregating: Combines legacy notification + theme settings into UnifiedSettings.
  • Conditional: Migrates app mode only when the version rule is met.
  • Manual Sweep: Explicitly triggers a "drain" of legacy keys to the Keychain using StorageRouter.shared.forceMigration(for:).
  • Startup Sweep: Automatically cleanses all registered legacy keys at app launch via registerCatalog(..., migrateImmediately: true).

Global Configuration

The app demonstrates how to configure the LocalData library globally during startup in SecureStorageSampleApp.swift:

  • Encryption: Customized Keychain service (SecureStorageSample) and account (AppMasterKey) names to isolate the library's master encryption key from other apps.
  • Sync: Set a custom maxAutoSyncSize of 50KB to control which data is automatically synchronized to the Apple Watch, overriding the library's 100KB default.
  • File Storage: Scoping all library files into a SecureStorage sub-directory. This ensures that the library's data (whether in the main sandbox or a shared App Group container) is kept neat and isolated within its own folder, rather than cluttering the root directories.
  • Storage Defaults: Pre-configuring the default Keychain service and App Group identifier. This allows common keys in the app to omit these identifiers, reducing boilerplate and making the code more maintainable.

This approach centralizes infrastructure settings and avoids hardcoding environment-specific values within individual storage keys.

Dependencies

  • LocalData - Local package for typed secure storage
  • SharedKit - Local package for shared iOS/watch models and constants

Notes

  • Storage keys are now split into one file per key and grouped by domain; platform-focused keys live in StorageKeys/Platform with comments calling out availability/sync focus.
  • Keys are declared as StorageKey<Value> static properties via constrained extensions (e.g., extension StorageKey where Value == String).
  • The shared model/constants live in SharedPackage (SharedKit) to keep the watch/iOS data contract centralized.
  • Keychain service IDs and App Group identifiers are centralized in SharedKit/Constants/StorageServiceIdentifiers.swift to avoid hardcoded strings in keys.
  • The watch app uses a handler-based WatchConnectivity layer so new payload types can be added in Services/Handlers without bloating the main service.
  • A StorageKeyCatalog sample is included to generate a security audit report of all storage keys.
  • Each StorageKey includes a description used in audit reports.
  • The catalog is registered at app startup to enforce key registration and catch duplicates.

License

This sample is provided for demonstration purposes.