Skip to content

iOS Integration: Implement Key Rotation Workflow Integration #1064

@lmcmz

Description

@lmcmz

Overview

Integrate the key rotation workflow into the iOS React Native application. Implement native iOS-specific features for app launch detection, secure seed phrase handling, and seamless integration with the shared UI components and workflow logic.

Background

iOS users with Blocto keys need to be seamlessly guided through the key rotation process when the app launches. This requires integrating the key rotation workflow with iOS app lifecycle events, native secure storage, and platform-specific security features.

Technical Requirements

📱 iOS Native Integration

  • App Launch Detection: Detect Blocto keys when app becomes active
  • Secure Storage: Use iOS Keychain for sensitive seed phrase operations
  • Background Task Handling: Handle key rotation in background when app is backgrounded
  • Biometric Authentication: Integrate with Touch ID/Face ID for seed phrase access

🔄 Workflow Integration

  • React Native Bridge: Connect iOS native code with key rotation workflows
  • State Synchronization: Sync rotation state between native and JS contexts
  • Progress Tracking: Native progress tracking for long-running operations
  • Error Handling: Native error handling with proper user feedback

🛡️ iOS Security Features

  • Keychain Integration: Store seed phrases in iOS Keychain with proper access controls
  • Secure Enclave: Utilize Secure Enclave for key operations where available
  • App Transport Security: Ensure all network operations follow ATS requirements
  • Data Protection: Implement proper data protection classes for sensitive operations

Implementation Details

📁 File Structure

apps/react-native/ios/
├── KeyRotation/
│   ├── KeyRotationManager.swift      # Main rotation manager
│   ├── BloctoDetector.swift          # iOS-specific Blocto detection
│   ├── SecureStorage.swift           # Keychain integration
│   ├── BiometricAuth.swift           # Touch ID/Face ID integration
│   └── KeyRotationBridge.m           # React Native bridge
├── AppLifecycle/
│   ├── AppLaunchHandler.swift        # Handle app launch events
│   └── BackgroundTaskManager.swift   # Background task handling
└── Security/
    ├── KeychainManager.swift         # Keychain operations
    └── SecureEnclaveManager.swift    # Secure Enclave operations

🚀 App Launch Integration

AppDelegate Integration

// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
  private let keyRotationManager = KeyRotationManager()
  
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // Initialize key rotation manager
    keyRotationManager.initialize()
    
    // Schedule Blocto key detection for when app becomes active
    NotificationCenter.default.addObserver(
      forName: UIApplication.didBecomeActiveNotification,
      object: nil,
      queue: .main
    ) { _ in
      Task {
        await self.keyRotationManager.checkForBloctoKeys()
      }
    }
    
    return true
  }
}

App Launch Handler

import UIKit
import Foundation

class AppLaunchHandler {
  private let keyRotationManager: KeyRotationManager
  private var hasCheckedThisSession = false
  
  init(keyRotationManager: KeyRotationManager) {
    self.keyRotationManager = keyRotationManager
  }
  
  func handleAppBecameActive() async {
    // Only check once per app session to avoid repeated prompts
    guard !hasCheckedThisSession else { return }
    hasCheckedThisSession = true
    
    // Check for Blocto keys in background
    await performBloctoKeyDetection()
  }
  
  private func performBloctoKeyDetection() async {
    do {
      let userProfile = await getCurrentUserProfile()
      let detectionResult = await keyRotationManager.detectBloctoKey(for: userProfile)
      
      if detectionResult.isBloctoKey && detectionResult.shouldPromptRotation {
        // Dispatch to main queue for UI updates
        await MainActor.run {
          keyRotationManager.presentRotationPrompt(with: detectionResult)
        }
      }
    } catch {
      logger.error("Blocto key detection failed: \\(error)")
    }
  }
}

🔐 Secure Storage Implementation

iOS Keychain Integration

import Security
import Foundation

class SecureKeychainManager {
  private let service = "com.flowfoundation.wallet.keyrotation"
  
  func storeSeedPhrase(_ seedPhrase: [String], forAddress address: String) throws {
    let seedData = try JSONEncoder().encode(seedPhrase)
    
    let query: [String: Any] = [
      kSecClass as String: kSecClassGenericPassword,
      kSecAttrService as String: service,
      kSecAttrAccount as String: "seedphrase_\\(address)",
      kSecValueData as String: seedData,
      kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
      kSecAttrSynchronizable as String: false // Don't sync via iCloud
    ]
    
    let status = SecItemAdd(query as CFDictionary, nil)
    
    guard status == errSecSuccess else {
      throw KeychainError.storageError(status)
    }
  }
  
  func retrieveSeedPhrase(forAddress address: String) throws -> [String] {
    let query: [String: Any] = [
      kSecClass as String: kSecClassGenericPassword,
      kSecAttrService as String: service,
      kSecAttrAccount as String: "seedphrase_\\(address)",
      kSecReturnData as String: true,
      kSecMatchLimit as String: kSecMatchLimitOne
    ]
    
    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)
    
    guard status == errSecSuccess,
          let data = result as? Data else {
      throw KeychainError.retrievalError(status)
    }
    
    return try JSONDecoder().decode([String].self, from: data)
  }
  
  func deleteSeedPhrase(forAddress address: String) throws {
    let query: [String: Any] = [
      kSecClass as String: kSecClassGenericPassword,
      kSecAttrService as String: service,
      kSecAttrAccount as String: "seedphrase_\\(address)"
    ]
    
    let status = SecItemDelete(query as CFDictionary)
    
    guard status == errSecSuccess || status == errSecItemNotFound else {
      throw KeychainError.deletionError(status)
    }
  }
}

enum KeychainError: Error {
  case storageError(OSStatus)
  case retrievalError(OSStatus)
  case deletionError(OSStatus)
}

Biometric Authentication

import LocalAuthentication

class BiometricAuthManager {
  func authenticateForSeedPhrase() async throws -> Bool {
    let context = LAContext()
    var error: NSError?
    
    // Check if biometric authentication is available
    guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
      throw BiometricError.biometricUnavailable(error?.localizedDescription)
    }
    
    do {
      let result = try await context.evaluatePolicy(
        .deviceOwnerAuthenticationWithBiometrics,
        localizedReason: "Authenticate to access your seed phrase for key rotation"
      )
      return result
    } catch {
      throw BiometricError.authenticationFailed(error.localizedDescription)
    }
  }
  
  func authenticateForKeyRotation() async throws -> Bool {
    let context = LAContext()
    
    do {
      let result = try await context.evaluatePolicy(
        .deviceOwnerAuthentication,
        localizedReason: "Authenticate to complete key rotation"
      )
      return result
    } catch {
      throw BiometricError.authenticationFailed(error.localizedDescription)
    }
  }
}

enum BiometricError: Error, LocalizedError {
  case biometricUnavailable(String?)
  case authenticationFailed(String)
  
  var errorDescription: String? {
    switch self {
    case .biometricUnavailable(let reason):
      return "Biometric authentication unavailable: \\(reason ?? "Unknown")"
    case .authenticationFailed(let reason):
      return "Authentication failed: \\(reason)"
    }
  }
}

🔄 Key Rotation Manager

Main Rotation Manager

import Foundation
import React

@objc(KeyRotationManager)
class KeyRotationManager: NSObject {
  private let secureStorage = SecureKeychainManager()
  private let biometricAuth = BiometricAuthManager()
  private let backgroundTaskManager = BackgroundTaskManager()
  
  // React Native bridge reference
  private weak var bridge: RCTBridge?
  
  func initialize() {
    // Initialize any required services
    setupNotificationObservers()
  }
  
  func detectBloctoKey(for userProfile: UserProfile) async -> BloctoDetectionResult {
    // Call into workflow package via React Native bridge
    return await withCheckedContinuation { continuation in
      bridge?.enqueueJSCall("KeyRotationWorkflow", 
                           method: "detectBloctoKey",
                           args: [userProfile.toDictionary()]) { result, error in
        if let error = error {
          continuation.resume(returning: BloctoDetectionResult.error(error))
        } else if let resultDict = result as? [String: Any] {
          continuation.resume(returning: BloctoDetectionResult.from(resultDict))
        }
      }
    }
  }
  
  func presentRotationPrompt(with result: BloctoDetectionResult) {
    // Present key rotation UI via React Native
    bridge?.enqueueJSCall("KeyRotationUI",
                         method: "presentRotationPrompt",
                         args: [result.toDictionary()],
                         completion: nil)
  }
  
  @objc func startKeyRotation(_ params: [String: Any], 
                             resolver: @escaping RCTPromiseResolveBlock,
                             rejecter: @escaping RCTPromiseRejectBlock) {
    Task {
      do {
        // Authenticate user before starting rotation
        let authenticated = try await biometricAuth.authenticateForKeyRotation()
        guard authenticated else {
          rejecter("AUTH_FAILED", "Authentication required for key rotation", nil)
          return
        }
        
        // Start background task for long-running operation
        let backgroundTask = await backgroundTaskManager.beginBackgroundTask(
          name: "KeyRotation"
        )
        
        defer {
          Task {
            await backgroundTaskManager.endBackgroundTask(backgroundTask)
          }
        }
        
        // Execute rotation workflow
        let result = await executeKeyRotation(params)
        
        // Store new seed phrase securely
        if let newSeedPhrase = result["newSeedPhrase"] as? [String],
           let newAddress = result["newAddress"] as? String {
          try secureStorage.storeSeedPhrase(newSeedPhrase, forAddress: newAddress)
        }
        
        // Clean up old address data
        if let oldAddress = result["oldAddress"] as? String {
          try secureStorage.deleteSeedPhrase(forAddress: oldAddress)
        }
        
        resolver(result)
        
      } catch {
        rejecter("ROTATION_FAILED", error.localizedDescription, error)
      }
    }
  }
  
  private func executeKeyRotation(_ params: [String: Any]) async -> [String: Any] {
    // Execute rotation via workflow package
    return await withCheckedContinuation { continuation in
      bridge?.enqueueJSCall("KeyRotationWorkflow",
                           method: "executeRotation",
                           args: [params]) { result, error in
        if let error = error {
          continuation.resume(returning: ["error": error.localizedDescription])
        } else if let resultDict = result as? [String: Any] {
          continuation.resume(returning: resultDict)
        } else {
          continuation.resume(returning: ["error": "Unknown result type"])
        }
      }
    }
  }
}

🌉 React Native Bridge

Bridge Implementation

// KeyRotationBridge.m
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(KeyRotationManager, NSObject)

RCT_EXTERN_METHOD(startKeyRotation:(NSDictionary *)params
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(checkBiometricAvailability:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(authenticateUser:(NSString *)reason
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)

@end

📱 Background Task Management

Background Task Handler

import UIKit

class BackgroundTaskManager {
  private var backgroundTasks: [UIBackgroundTaskIdentifier: String] = [:]
  
  func beginBackgroundTask(name: String) async -> UIBackgroundTaskIdentifier {
    return await withCheckedContinuation { continuation in
      DispatchQueue.main.async {
        let taskId = UIApplication.shared.beginBackgroundTask(withName: name) {
          // Background task expired
          self.endBackgroundTask(taskId)
        }
        
        if taskId != .invalid {
          self.backgroundTasks[taskId] = name
        }
        
        continuation.resume(returning: taskId)
      }
    }
  }
  
  func endBackgroundTask(_ taskId: UIBackgroundTaskIdentifier) async {
    await MainActor.run {
      guard taskId != .invalid else { return }
      
      backgroundTasks.removeValue(forKey: taskId)
      UIApplication.shared.endBackgroundTask(taskId)
    }
  }
}

Security & Performance

🔒 Security Features

  • Keychain Access Control: Proper access control for stored seed phrases
  • Biometric Authentication: Secure authentication for sensitive operations
  • Data Protection: iOS data protection classes for sensitive data
  • Network Security: App Transport Security compliance

⚡ Performance Optimization

  • Background Processing: Handle detection and rotation in background
  • Memory Management: Proper cleanup of sensitive data in memory
  • Battery Efficiency: Optimize battery usage during key rotation
  • Network Efficiency: Minimize network calls during detection

Testing Requirements

🧪 iOS-Specific Testing

  • Keychain Testing: Test secure storage operations
  • Biometric Testing: Test Touch ID/Face ID integration
  • Background Testing: Test background task handling
  • App Lifecycle Testing: Test app launch and backgrounding scenarios

📱 Device Testing

  • iPhone Testing: Test on various iPhone models
  • iOS Version Testing: Test on different iOS versions
  • Biometric Variations: Test Touch ID and Face ID devices
  • Memory Constraints: Test on devices with limited memory

Acceptance Criteria

  • App detects Blocto keys on launch and presents appropriate prompts
  • Key rotation workflow integrates seamlessly with iOS security features
  • Seed phrases are stored securely in iOS Keychain with proper access controls
  • Biometric authentication works correctly for key rotation operations
  • Background tasks handle long-running operations without termination
  • Error handling provides clear feedback to users for all failure scenarios
  • Performance is optimized for iOS devices with minimal battery impact
  • Integration with shared UI components works correctly
  • All sensitive operations follow iOS security best practices

Dependencies

Related Issues


🤖 Generated with Claude Code

Co-Authored-By: Claude [email protected]

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions