Files
Portal-Camera/mac/PortalCam/CamExtension/PortalAuth.swift
T
2026-09-13 12:15:36 -07:00

84 lines
3.7 KiB
Swift

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
}
}