* Added a SecItem enum to wrap existing security API functions
* Updated the SecError to easily create an error from an OSStatus
* Updated the SecError.Code to be more future proof and performant
* Removed the unused Keychain.swift file
This commit is contained in:
Bram Kolkman
2026-02-23 14:09:05 +01:00
parent af8a434577
commit a7cdef977b
4 changed files with 512 additions and 388 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ public struct SecError: Error, Hashable, Sendable {
/// The error code.
public let code: Code
/// The description of the error.
public var errorDescription: String? {
@inlinable public var errorDescription: String? {
SecCopyErrorMessageString(code.rawValue, nil) as? String
}
@@ -21,4 +21,10 @@ public struct SecError: Error, Hashable, Sendable {
init(_ code: Code) {
self.code = code
}
/// Create the error.
/// - Parameter status: The raw status code.
init(_ status: OSStatus) {
self.init(Code(rawValue: status))
}
}

View File

@@ -1,6 +0,0 @@
//
// Keychain.swift
// Keychain
//
// Created by Bram Kolkman on 20/02/2026.
//

View File

@@ -0,0 +1,115 @@
//
// SecItem.swift
// Keychain
//
// Created by Bram Kolkman on 23/02/2026.
//
import Security
public enum SecItem {
/// Adds an item to the keychain.
///
/// Use this function to store credentials, keys, certificates, and other secure data in the keychain.
/// The keychain automatically encrypts the data and manages access control.
///
/// - Parameter attributes: A dictionary containing the attributes of the item to add. At minimum,
/// include `kSecClass` to specify the item class and `kSecValueData` or `kSecValueRef` for the item's value.
/// Additional attributes control access, persistence, and synchronization behavior.
///
/// - Returns: If you include the `kSecReturnAttributes`, `kSecReturnData`, `kSecReturnRef`, or
/// `kSecReturnPersistentRef` key with a value of `true` in the attributes dictionary, this function
/// returns the requested data. Otherwise, it returns `nil`.
///
/// - Throws: A `SecError` if the item could not be added. Common errors include duplicate items
/// (`errSecDuplicateItem`) or invalid parameters (`errSecParam`).
@discardableResult
public static func add(_ attributes: [CFString: Any]) throws -> CFTypeRef? {
var result: CFTypeRef?
let status = SecItemAdd(attributes as CFDictionary, &result)
switch status {
case errSecSuccess:
return result
default:
throw SecError(status)
}
}
/// Searches for and retrieves items from the keychain.
///
/// Use this function to find keychain items that match specified search criteria. You can retrieve
/// the item's data, attributes, or a persistent reference that you can use for later access.
///
/// - Parameter query: A dictionary containing the search criteria. At minimum, include `kSecClass`
/// to specify the item class. Add search attributes like `kSecAttrAccount` or `kSecAttrService`
/// to narrow results. Include return type keys (`kSecReturnData`, `kSecReturnAttributes`,
/// `kSecReturnRef`, or `kSecReturnPersistentRef`) to specify what data to return.
///
/// - Returns: The requested keychain item data, attributes, or reference as specified by the return
/// type keys in the query. Returns `nil` if no matching item is found (`errSecItemNotFound`).
///
/// - Throws: A `SecError` if the search fails for reasons other than the item not being found.
/// Common errors include invalid parameters (`errSecParam`) or user interaction cancelled
/// (`errSecUserCanceled`).
public static func copy(matching query: [CFString: Any]) throws (SecError) -> CFTypeRef? {
var result: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &result)
switch status {
case errSecSuccess:
return result
case errSecItemNotFound:
return nil
default:
throw SecError(status)
}
}
/// Modifies items in the keychain that match a search query.
///
/// Use this function to change the attributes or data of existing keychain items. The function
/// finds items matching the search criteria and applies the specified updates to them.
///
/// - Parameter query: A dictionary containing the search criteria for items to update. At minimum,
/// include `kSecClass` to specify the item class. Add search attributes to identify specific items.
/// Do not include return type keys in the query dictionary.
///
/// - Parameter updates: A dictionary containing the attributes to modify. Include only the attributes
/// you want to change. You cannot modify the `kSecClass` attribute.
///
/// - Throws: A `SecError` if the update fails. Common errors include item not found (`errSecItemNotFound`),
/// invalid parameters (`errSecParam`), or attempting to create a duplicate item.
public static func update(matching query: [CFString: Any], with updates: [CFString: Any]) throws {
let status = SecItemUpdate(query as CFDictionary, updates as CFDictionary)
switch status {
case errSecSuccess:
break
default:
throw SecError(status)
}
}
/// Removes items from the keychain that match a search query.
///
/// Use this function to delete keychain items that are no longer needed. The function finds all items
/// matching the search criteria and removes them from the keychain. If no matching items exist,
/// the function succeeds without error.
///
/// - Parameter query: A dictionary containing the search criteria for items to delete. At minimum,
/// include `kSecClass` to specify the item class. Add search attributes to identify specific items.
/// To delete all items of a class, specify only `kSecClass`.
///
/// - Throws: A `SecError` if the deletion fails for reasons other than the item not being found.
/// Common errors include invalid parameters (`errSecParam`) or user interaction required
/// (`errSecInteractionNotAllowed`).
public static func delete(matching query: [CFString: Any]) throws {
let status = SecItemDelete(query as CFDictionary)
switch status {
case errSecSuccess, errSecItemNotFound:
break
default:
throw SecError(status)
}
}
}