Files
native/plugins/ios-push/SharedKeychain.swift

55 lines
2.0 KiB
Swift

import Foundation
import Security
/// Items the app shares with this extension through a keychain access group
/// (BulwarkKeychainGroup in Info.plist). The app writes them with
/// expo-secure-store: generic passwords whose account and generic attributes
/// are the UTF-8 key and whose service is "app:no-auth".
enum SharedKeychain {
static let service = "app:no-auth"
static var group: String? {
Bundle.main.object(forInfoDictionaryKey: "BulwarkKeychainGroup") as? String
}
static func read(_ key: String) -> Data? {
guard let group else { return nil }
let account = Data(key.utf8)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecAttrAccessGroup as String: group,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnData as String: true,
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else {
pushLog.error("keychain read of \(key, privacy: .public) failed: \(status, privacy: .public)")
return nil
}
return item as? Data
}
static func readJSON(_ key: String) -> [String: Any]? {
guard let data = read(key) else { return nil }
return (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
}
/// Replaces the value of an existing item (refreshed OAuth tokens). Never
/// creates one: the app owns which items exist.
@discardableResult
static func update(_ key: String, json: [String: Any]) -> Bool {
guard let group, let data = try? JSONSerialization.data(withJSONObject: json) else { return false }
let account = Data(key.utf8)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecAttrAccessGroup as String: group,
]
let attributes: [String: Any] = [kSecValueData as String: data]
return SecItemUpdate(query as CFDictionary, attributes as CFDictionary) == errSecSuccess
}
}