import Foundation import Security import os.log /// Authentication shared with the containing app through the Keychain access group. /// The extension accesses the bearer token and pinned certificate fingerprint stored by the app. enum PortalAuth { static let suite = "ENT9X9U544.com.kovtash.portalcam" static let accessGroup = "ENT9X9U544.com.kovtash.portalcam" static let service = "com.kovtash.portalcam.auth" static let tokenKey = "portalAuthToken" static let certKey = "portalPinnedCertSha256" static var token: String? { return readItem(account: tokenKey) } static var pinnedCertSha256: String? { return readItem(account: certKey) } private static func readItem(account: String) -> String? { // 1. Try modern Data Protection Keychain with explicit access group let dpQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, kSecAttrAccessGroup as String: accessGroup, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne, kSecUseDataProtectionKeychain as String: true ] var item: CFTypeRef? let dpStatus = SecItemCopyMatching(dpQuery as CFDictionary, &item) if dpStatus == errSecSuccess, let data = item as? Data, let value = String(data: data, encoding: .utf8) { os_log(.default, "PortalCam extension: %{public}@ found in DP Keychain with access group", account) return value } // 2. Try Data Protection Keychain without explicit access group (matches all groups in entitlement) let defaultGroupQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne, kSecUseDataProtectionKeychain as String: true ] var defaultItem: CFTypeRef? let defaultStatus = SecItemCopyMatching(defaultGroupQuery as CFDictionary, &defaultItem) if defaultStatus == errSecSuccess, let data = defaultItem as? Data, let value = String(data: data, encoding: .utf8) { os_log(.default, "PortalCam extension: %{public}@ found in DP Keychain default group", account) return value } // 3. Try legacy file-based keychain let legacyQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne ] var legacyItem: CFTypeRef? let legacyStatus = SecItemCopyMatching(legacyQuery as CFDictionary, &legacyItem) if legacyStatus == errSecSuccess, let data = legacyItem as? Data, let value = String(data: data, encoding: .utf8) { os_log(.default, "PortalCam extension: %{public}@ found in Legacy Keychain", account) return value } // 4. Compatibility fallback: App Group UserDefaults if let value = UserDefaults(suiteName: suite)?.string(forKey: account) { os_log(.default, "PortalCam extension: %{public}@ found in App Group fallback", account) return value } os_log(.default, "PortalCam extension: %{public}@ unavailable (DP status: %{public}d, default status: %{public}d, legacy status: %{public}d)", account, dpStatus, defaultStatus, legacyStatus) return nil } }