ios/BitwardenShared/Core/Auth/Services/KeychainService.swift
2025-06-10 12:03:49 -05:00

139 lines
3.8 KiB
Swift

import AuthenticatorBridgeKit
import BitwardenKit
import Foundation
// MARK: - KeychainService
/// A Service to provide a wrapper around the device Keychain.
///
protocol KeychainService: AnyObject {
/// Creates an access control for a given set of flags.
///
/// - Parameters:
/// - protection: Protection class to be used for the item. Use one of the values that go with the
/// `kSecAttrAccessible` attribute key.
/// - flags: The `SecAccessControlCreateFlags` for the access control.
/// - Returns: The SecAccessControl.
///
func accessControl(
protection: CFTypeRef,
for flags: SecAccessControlCreateFlags
) throws -> SecAccessControl
/// Adds a set of attributes.
///
/// - Parameter attributes: Attributes to add.
///
func add(attributes: CFDictionary) throws
/// Attempts a deletion based on a query.
///
/// - Parameter query: Query for the delete.
///
func delete(query: CFDictionary) throws
/// Searches for a query.
///
/// - Parameter query: Query for the delete.
/// - Returns: The search results.
///
func search(query: CFDictionary) throws -> AnyObject?
}
// MARK: - KeychainServiceError
enum KeychainServiceError: Error, Equatable, CustomNSError {
/// When creating an accessControl fails.
///
/// - Parameter CFError: The potential system error.
///
case accessControlFailed(CFError?)
/// When a `KeychainService` is unable to locate an auth key for a given storage key.
///
/// - Parameter KeychainItem: The potential storage key for the auth key.
///
case keyNotFound(KeychainItem)
/// A passthrough for OSService Error cases.
///
/// - Parameter OSStatus: The `OSStatus` returned from a keychain operation.
///
case osStatusError(OSStatus)
/// The user-info dictionary.
public var errorUserInfo: [String: Any] {
switch self {
case .accessControlFailed:
return [:]
case let .keyNotFound(keychainItem):
return ["Keychain Item": keychainItem.unformattedKey]
case let .osStatusError(osStatus):
return ["OS Status": osStatus]
}
}
}
// MARK: - DefaultKeychainService
class DefaultKeychainService: KeychainService {
// MARK: Methods
func accessControl(
protection: CFTypeRef,
for flags: SecAccessControlCreateFlags
) throws -> SecAccessControl {
var error: Unmanaged<CFError>?
let accessControl = SecAccessControlCreateWithFlags(
nil,
protection,
flags,
&error
)
guard let accessControl,
error == nil
else {
throw KeychainServiceError.accessControlFailed(error?.takeUnretainedValue())
}
return accessControl
}
func add(attributes: CFDictionary) throws {
try resolve(SecItemAdd(attributes, nil))
}
func delete(query: CFDictionary) throws {
let status = SecItemDelete(query)
guard [errSecSuccess, errSecItemNotFound].contains(status) else {
throw KeychainServiceError.osStatusError(status)
}
}
func search(query: CFDictionary) throws -> AnyObject? {
var foundItem: AnyObject?
try resolve(SecItemCopyMatching(query, &foundItem))
return foundItem
}
// MARK: Private Methods
/// Ensures that a given status is a success.
/// Throws if not `errSecSuccess`.
///
/// - Parameter status: The OSStatus to check.
///
private func resolve(_ status: OSStatus) throws {
switch status {
case errSecSuccess:
break
default:
throw KeychainServiceError.osStatusError(status)
}
}
}
// MARK: - SharedKeychainService
extension DefaultKeychainService: SharedKeychainService {}