diff --git a/.gitignore b/.gitignore
index 67356a1..70e6dad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,6 @@
.build/
## Various settings
-Package.resolved
*.pbxuser
!default.pbxuser
*.mode1v3
diff --git a/.swift-format b/.swift-format
deleted file mode 100644
index b77abe6..0000000
--- a/.swift-format
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "fileScopedDeclarationPrivacy": {
- "accessLevel": "private"
- },
- "indentBlankLines": true,
- "indentation": {
- "spaces": 4
- },
- "lineLength": 9999,
- "maximumBlankLines": 1,
- "multiElementCollectionTrailingCommas": false,
- "rules": {
- "FileScopedDeclarationPrivacy": true
- }
-}
diff --git a/KeychainKit.xcodeproj/KeychainKit_Info.plist b/KeychainKit.xcodeproj/KeychainKit_Info.plist
new file mode 100644
index 0000000..57ada9f
--- /dev/null
+++ b/KeychainKit.xcodeproj/KeychainKit_Info.plist
@@ -0,0 +1,25 @@
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/KeychainKit.xcodeproj/project.pbxproj b/KeychainKit.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..2b99a90
--- /dev/null
+++ b/KeychainKit.xcodeproj/project.pbxproj
@@ -0,0 +1,383 @@
+// !$*UTF8*$!
+{
+ archiveVersion = "1";
+ objectVersion = "46";
+ objects = {
+ "KeychainKit::KeychainKit" = {
+ isa = "PBXNativeTarget";
+ buildConfigurationList = "OBJ_16";
+ buildPhases = (
+ "OBJ_19",
+ "OBJ_21"
+ );
+ dependencies = (
+ );
+ name = "KeychainKit";
+ productName = "KeychainKit";
+ productReference = "KeychainKit::KeychainKit::Product";
+ productType = "com.apple.product-type.framework";
+ };
+ "KeychainKit::KeychainKit::Product" = {
+ isa = "PBXFileReference";
+ path = "KeychainKit.framework";
+ sourceTree = "BUILT_PRODUCTS_DIR";
+ };
+ "KeychainKit::SwiftPMPackageDescription" = {
+ isa = "PBXNativeTarget";
+ buildConfigurationList = "OBJ_23";
+ buildPhases = (
+ "OBJ_26"
+ );
+ dependencies = (
+ );
+ name = "KeychainKitPackageDescription";
+ productName = "KeychainKitPackageDescription";
+ productType = "com.apple.product-type.framework";
+ };
+ "OBJ_1" = {
+ isa = "PBXProject";
+ attributes = {
+ LastSwiftMigration = "9999";
+ LastUpgradeCheck = "9999";
+ };
+ buildConfigurationList = "OBJ_2";
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = "en";
+ hasScannedForEncodings = "0";
+ knownRegions = (
+ "en"
+ );
+ mainGroup = "OBJ_5";
+ productRefGroup = "OBJ_11";
+ projectDirPath = ".";
+ targets = (
+ "KeychainKit::KeychainKit",
+ "KeychainKit::SwiftPMPackageDescription"
+ );
+ };
+ "OBJ_10" = {
+ isa = "PBXGroup";
+ children = (
+ );
+ name = "Tests";
+ path = "";
+ sourceTree = "SOURCE_ROOT";
+ };
+ "OBJ_11" = {
+ isa = "PBXGroup";
+ children = (
+ "KeychainKit::KeychainKit::Product"
+ );
+ name = "Products";
+ path = "";
+ sourceTree = "BUILT_PRODUCTS_DIR";
+ };
+ "OBJ_13" = {
+ isa = "PBXFileReference";
+ path = "LICENSE";
+ sourceTree = "";
+ };
+ "OBJ_14" = {
+ isa = "PBXFileReference";
+ path = "README.md";
+ sourceTree = "";
+ };
+ "OBJ_16" = {
+ isa = "XCConfigurationList";
+ buildConfigurations = (
+ "OBJ_17",
+ "OBJ_18"
+ );
+ defaultConfigurationIsVisible = "0";
+ defaultConfigurationName = "Release";
+ };
+ "OBJ_17" = {
+ isa = "XCBuildConfiguration";
+ buildSettings = {
+ ENABLE_TESTABILITY = "YES";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PLATFORM_DIR)/Developer/Library/Frameworks"
+ );
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)"
+ );
+ INFOPLIST_FILE = "KeychainKit.xcodeproj/KeychainKit_Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = "8.0";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"
+ );
+ MACOSX_DEPLOYMENT_TARGET = "10.10";
+ OTHER_CFLAGS = (
+ "$(inherited)"
+ );
+ OTHER_LDFLAGS = (
+ "$(inherited)"
+ );
+ OTHER_SWIFT_FLAGS = (
+ "$(inherited)"
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "KeychainKit";
+ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = "YES";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
+ "$(inherited)"
+ );
+ SWIFT_VERSION = "5.0";
+ TARGET_NAME = "KeychainKit";
+ TVOS_DEPLOYMENT_TARGET = "9.0";
+ WATCHOS_DEPLOYMENT_TARGET = "2.0";
+ };
+ name = "Debug";
+ };
+ "OBJ_18" = {
+ isa = "XCBuildConfiguration";
+ buildSettings = {
+ ENABLE_TESTABILITY = "YES";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PLATFORM_DIR)/Developer/Library/Frameworks"
+ );
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)"
+ );
+ INFOPLIST_FILE = "KeychainKit.xcodeproj/KeychainKit_Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = "8.0";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"
+ );
+ MACOSX_DEPLOYMENT_TARGET = "10.10";
+ OTHER_CFLAGS = (
+ "$(inherited)"
+ );
+ OTHER_LDFLAGS = (
+ "$(inherited)"
+ );
+ OTHER_SWIFT_FLAGS = (
+ "$(inherited)"
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "KeychainKit";
+ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = "YES";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
+ "$(inherited)"
+ );
+ SWIFT_VERSION = "5.0";
+ TARGET_NAME = "KeychainKit";
+ TVOS_DEPLOYMENT_TARGET = "9.0";
+ WATCHOS_DEPLOYMENT_TARGET = "2.0";
+ };
+ name = "Release";
+ };
+ "OBJ_19" = {
+ isa = "PBXSourcesBuildPhase";
+ files = (
+ "OBJ_20"
+ );
+ };
+ "OBJ_2" = {
+ isa = "XCConfigurationList";
+ buildConfigurations = (
+ "OBJ_3",
+ "OBJ_4"
+ );
+ defaultConfigurationIsVisible = "0";
+ defaultConfigurationName = "Release";
+ };
+ "OBJ_20" = {
+ isa = "PBXBuildFile";
+ fileRef = "OBJ_9";
+ };
+ "OBJ_21" = {
+ isa = "PBXFrameworksBuildPhase";
+ files = (
+ );
+ };
+ "OBJ_23" = {
+ isa = "XCConfigurationList";
+ buildConfigurations = (
+ "OBJ_24",
+ "OBJ_25"
+ );
+ defaultConfigurationIsVisible = "0";
+ defaultConfigurationName = "Release";
+ };
+ "OBJ_24" = {
+ isa = "XCBuildConfiguration";
+ buildSettings = {
+ LD = "/usr/bin/true";
+ OTHER_SWIFT_FLAGS = (
+ "-swift-version",
+ "5",
+ "-I",
+ "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2",
+ "-target",
+ "x86_64-apple-macosx10.10",
+ "-sdk",
+ "/Users/mr.noone/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk",
+ "-package-description-version",
+ "5.2.0"
+ );
+ SWIFT_VERSION = "5.0";
+ };
+ name = "Debug";
+ };
+ "OBJ_25" = {
+ isa = "XCBuildConfiguration";
+ buildSettings = {
+ LD = "/usr/bin/true";
+ OTHER_SWIFT_FLAGS = (
+ "-swift-version",
+ "5",
+ "-I",
+ "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2",
+ "-target",
+ "x86_64-apple-macosx10.10",
+ "-sdk",
+ "/Users/mr.noone/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk",
+ "-package-description-version",
+ "5.2.0"
+ );
+ SWIFT_VERSION = "5.0";
+ };
+ name = "Release";
+ };
+ "OBJ_26" = {
+ isa = "PBXSourcesBuildPhase";
+ files = (
+ "OBJ_27"
+ );
+ };
+ "OBJ_27" = {
+ isa = "PBXBuildFile";
+ fileRef = "OBJ_6";
+ };
+ "OBJ_3" = {
+ isa = "XCBuildConfiguration";
+ buildSettings = {
+ CLANG_ENABLE_OBJC_ARC = "YES";
+ COMBINE_HIDPI_IMAGES = "YES";
+ COPY_PHASE_STRIP = "NO";
+ DEBUG_INFORMATION_FORMAT = "dwarf";
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_NS_ASSERTIONS = "YES";
+ GCC_OPTIMIZATION_LEVEL = "0";
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "SWIFT_PACKAGE=1",
+ "DEBUG=1"
+ );
+ MACOSX_DEPLOYMENT_TARGET = "10.10";
+ ONLY_ACTIVE_ARCH = "YES";
+ OTHER_SWIFT_FLAGS = (
+ "$(inherited)",
+ "-DXcode"
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = "macosx";
+ SUPPORTED_PLATFORMS = (
+ "macosx",
+ "iphoneos",
+ "iphonesimulator",
+ "appletvos",
+ "appletvsimulator",
+ "watchos",
+ "watchsimulator"
+ );
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
+ "$(inherited)",
+ "SWIFT_PACKAGE",
+ "DEBUG"
+ );
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ USE_HEADERMAP = "NO";
+ };
+ name = "Debug";
+ };
+ "OBJ_4" = {
+ isa = "XCBuildConfiguration";
+ buildSettings = {
+ CLANG_ENABLE_OBJC_ARC = "YES";
+ COMBINE_HIDPI_IMAGES = "YES";
+ COPY_PHASE_STRIP = "YES";
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ GCC_OPTIMIZATION_LEVEL = "s";
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "SWIFT_PACKAGE=1"
+ );
+ MACOSX_DEPLOYMENT_TARGET = "10.10";
+ OTHER_SWIFT_FLAGS = (
+ "$(inherited)",
+ "-DXcode"
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = "macosx";
+ SUPPORTED_PLATFORMS = (
+ "macosx",
+ "iphoneos",
+ "iphonesimulator",
+ "appletvos",
+ "appletvsimulator",
+ "watchos",
+ "watchsimulator"
+ );
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
+ "$(inherited)",
+ "SWIFT_PACKAGE"
+ );
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ USE_HEADERMAP = "NO";
+ };
+ name = "Release";
+ };
+ "OBJ_5" = {
+ isa = "PBXGroup";
+ children = (
+ "OBJ_6",
+ "OBJ_7",
+ "OBJ_10",
+ "OBJ_11",
+ "OBJ_13",
+ "OBJ_14"
+ );
+ path = "";
+ sourceTree = "";
+ };
+ "OBJ_6" = {
+ isa = "PBXFileReference";
+ explicitFileType = "sourcecode.swift";
+ path = "Package.swift";
+ sourceTree = "";
+ };
+ "OBJ_7" = {
+ isa = "PBXGroup";
+ children = (
+ "OBJ_8"
+ );
+ name = "Sources";
+ path = "";
+ sourceTree = "SOURCE_ROOT";
+ };
+ "OBJ_8" = {
+ isa = "PBXGroup";
+ children = (
+ "OBJ_9"
+ );
+ name = "KeychainKit";
+ path = "Sources/KeychainKit";
+ sourceTree = "SOURCE_ROOT";
+ };
+ "OBJ_9" = {
+ isa = "PBXFileReference";
+ path = "Keychain.swift";
+ sourceTree = "";
+ };
+ };
+ rootObject = "OBJ_1";
+}
diff --git a/KeychainKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/KeychainKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..fe1aa71
--- /dev/null
+++ b/KeychainKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/KeychainKit.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/KeychainKit.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..a72dc2b
--- /dev/null
+++ b/KeychainKit.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
+
+
+
\ No newline at end of file
diff --git a/KeychainKit.xcodeproj/xcshareddata/xcschemes/KeychainKit-Package.xcscheme b/KeychainKit.xcodeproj/xcshareddata/xcschemes/KeychainKit-Package.xcscheme
new file mode 100644
index 0000000..45c3ade
--- /dev/null
+++ b/KeychainKit.xcodeproj/xcshareddata/xcschemes/KeychainKit-Package.xcscheme
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LICENSE b/LICENSE
index b033d92..7365fe7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2025 ANGD Dev
+Copyright (c) 2020 Aleksey Zgurskiy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Package.swift b/Package.swift
index 273919b..e11cace 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,28 +1,16 @@
-// swift-tools-version: 6.0
+// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
- name: "KeychainKit",
- defaultLocalization: "en",
- platforms: [.macOS(.v12), .iOS(.v15)],
- products: [
- .library(name: "KeychainKit", targets: ["KeychainKit"])
- ],
- dependencies: [
- .package(url: "https://github.com/angd-dev/localizable.git", from: "1.0.0"),
- .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0")
- ],
- targets: [
- .target(
- name: "KeychainKit",
- dependencies: [
- .product(name: "Localizable", package: "localizable")
- ],
- resources: [
- .process("Resources/Localizable.xcstrings")
- ]
- )
- ]
+ name: "KeychainKit",
+ platforms: [.iOS(.v8)],
+ products: [
+ .library(name: "KeychainKit", targets: ["KeychainKit"]),
+ ],
+ dependencies: [],
+ targets: [
+ .target(name: "KeychainKit", dependencies: [])
+ ]
)
diff --git a/README.md b/README.md
index c73e91a..f8af6be 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1 @@
-# KeychainKit
-
-KeychainKit is a type-safe, easy-to-use wrapper around Apple’s Keychain service that supports storing, retrieving, and deleting data with optional local authentication.
-
-## Overview
-
-This library enables working with Keychain without losing control over security settings while simplifying type-safe access to data types like `Data`, `String`, `UUID`, and any `Codable` types.
-
-It supports optional authentication via `LAContext`, allowing integration with Face ID, Touch ID, or device passcode.
-
-KeychainKit does not hide the complexity of Keychain operations but provides a clean API and convenient error handling via a custom `KeychainError` type.
-
-## Installation
-
-To add KeychainKit to your project, use Swift Package Manager (SPM).
-
-### Adding to an Xcode Project
-
-1. Open your project in Xcode.
-2. Navigate to the `File` menu and select `Add Package Dependencies`.
-3. Enter the repository URL: `https://github.com/angd-dev/keychain-kit.git`
-4. Choose the version to install (e.g., `3.0.0`).
-5. Add the library to your target module.
-
-### Adding to Package.swift
-
-If you are using Swift Package Manager with a `Package.swift` file, add the dependency like this:
-
-```swift
-// swift-tools-version: 5.10
-import PackageDescription
-
-let package = Package(
- name: "YourProject",
- dependencies: [
- .package(url: "https://github.com/angd-dev/keychain-kit.git", from: "3.0.0")
- ],
- targets: [
- .target(
- name: "YourTarget",
- dependencies: [
- .product(name: "KeychainKit", package: "keychain-kit")
- ]
- )
- ]
-)
-```
-
-## Additional Resources
-
-For more information and usage examples, see the [documentation](https://docs.angd.dev/?package=keychain-kit&version=3.0.0).
-
-## License
-
-This project is licensed under the MIT License. See the `LICENSE` file for details.
+# keychain-kit
\ No newline at end of file
diff --git a/Sources/KeychainKit/Classes/KeychainStorage.swift b/Sources/KeychainKit/Classes/KeychainStorage.swift
deleted file mode 100644
index 9788e58..0000000
--- a/Sources/KeychainKit/Classes/KeychainStorage.swift
+++ /dev/null
@@ -1,193 +0,0 @@
-import Foundation
-import LocalAuthentication
-import Security
-
-/// A service that provides access and management for keychain items.
-///
-/// This type provides direct access to the system keychain using `Security` and
-/// `LocalAuthentication` frameworks. It supports querying, inserting, deleting, and checking item
-/// existence, while handling authentication contexts and access controls automatically.
-///
-/// ## Topics
-///
-/// ### Initializers
-///
-/// - ``init(service:context:)``
-///
-/// ### Instance Properties
-///
-/// - ``service``
-/// - ``context``
-///
-/// ### Instance Methods
-///
-/// - ``get(by:)->Data?``
-/// - ``insert(_:by:)-(Data,_)``
-/// - ``delete(by:)``
-/// - ``exists(by:)``
-public final class KeychainStorage<
- Account: KeychainAccountProtocol,
- Service: KeychainServiceProtocol
->: KeychainStorageProtocol, @unchecked Sendable {
- // MARK: - Properties
-
- /// The service descriptor associated with this keychain storage.
- public let service: Service?
-
- /// The authentication context used for keychain operations.
- public let context: LAContext?
-
- // MARK: - Initialization
-
- /// Creates a new keychain storage instance.
- ///
- /// - Parameters:
- /// - service: The service descriptor that defines the keychain group and access settings.
- /// - context: The authentication context used for secure access, or `nil` to use a default one.
- public init(service: Service?, context: LAContext?) {
- self.service = service
- self.context = context
- }
-
- // MARK: - Methods
-
- /// Retrieves raw data for the given account.
- ///
- /// - Parameter account: The account descriptor identifying the stored item.
- /// - Returns: The stored data, or `nil` if no item exists.
- /// - Throws: ``KeychainError/invalidData`` if the retrieved value cannot be cast to `Data`.
- /// - Throws: ``KeychainError/authenticationFailed`` if user authentication fails.
- /// - Throws: ``KeychainError/osStatus(_:)`` for unexpected system errors.
- public func get(by account: Account) throws(KeychainError) -> Data? {
- var query: [CFString: Any] = [
- kSecClass: kSecClassGenericPassword,
- kSecAttrAccount: account.identifier,
- kSecAttrSynchronizable: account.synchronizable,
- kSecUseDataProtectionKeychain: true,
- kSecMatchLimit: kSecMatchLimitOne,
- kSecReturnData: true
- ]
-
- query[kSecAttrService] = service?.identifier
- query[kSecAttrAccessGroup] = service?.accessGroup
- query[kSecUseAuthenticationContext] = context
-
- var result: AnyObject?
-
- switch SecItemCopyMatching(query as CFDictionary, &result) {
- case errSecSuccess:
- if let data = result as? Data {
- return data
- } else {
- throw .invalidData
- }
- case errSecItemNotFound:
- return nil
- case errSecAuthFailed, errSecInteractionNotAllowed, errSecUserCanceled:
- throw .authenticationFailed
- case let status:
- throw .osStatus(status)
- }
- }
-
- /// Inserts raw data for the given account.
- ///
- /// - Parameters:
- /// - value: The data to store.
- /// - account: The account descriptor identifying the target item.
- /// - Throws: ``KeychainError/underlying(_:)`` if access control creation fails.
- /// - Throws: ``KeychainError/duplicateItem`` if an item with the same key already exists.
- /// - Throws: ``KeychainError/osStatus(_:)`` for unexpected system errors.
- public func insert(_ value: Data, by account: Account) throws(KeychainError) {
- var error: Unmanaged?
- let access = SecAccessControlCreateWithFlags(
- nil, account.protection, account.accessFlags, &error
- )
-
- guard let access else {
- let error = error?.takeRetainedValue()
- throw .underlying(error as? NSError)
- }
-
- var query: [CFString: Any] = [
- kSecClass: kSecClassGenericPassword,
- kSecAttrAccount: account.identifier,
- kSecAttrSynchronizable: account.synchronizable,
- kSecUseDataProtectionKeychain: true,
- kSecAttrAccessControl: access,
- kSecValueData: value
- ]
-
- query[kSecAttrService] = service?.identifier
- query[kSecAttrAccessGroup] = service?.accessGroup
- query[kSecUseAuthenticationContext] = context
-
- switch SecItemAdd(query as CFDictionary, nil) {
- case errSecSuccess:
- return
- case errSecDuplicateItem:
- throw .duplicateItem
- case let status:
- throw .osStatus(status)
- }
- }
-
- /// Deletes the item for the given account.
- ///
- /// - Parameter account: The account descriptor identifying the item to remove.
- /// - Throws: ``KeychainError/authenticationFailed`` if user authentication fails.
- /// - Throws: ``KeychainError/osStatus(_:)`` for unexpected system errors.
- public func delete(by account: Account) throws(KeychainError) {
- var query: [CFString: Any] = [
- kSecClass: kSecClassGenericPassword,
- kSecAttrAccount: account.identifier,
- kSecAttrSynchronizable: account.synchronizable,
- kSecUseDataProtectionKeychain: true
- ]
-
- query[kSecAttrService] = service?.identifier
- query[kSecAttrAccessGroup] = service?.accessGroup
- query[kSecUseAuthenticationContext] = context
-
- switch SecItemDelete(query as CFDictionary) {
- case errSecSuccess, errSecItemNotFound:
- return
- case errSecAuthFailed, errSecInteractionNotAllowed, errSecUserCanceled:
- throw .authenticationFailed
- case let status:
- throw .osStatus(status)
- }
- }
-
- /// Checks whether an item exists for the given account.
- ///
- /// - Parameter account: The account descriptor identifying the stored item.
- /// - Returns: `true` if the item exists; otherwise, `false`.
- /// - Throws: ``KeychainError/osStatus(_:)`` for unexpected system errors.
- public func exists(by account: Account) throws(KeychainError) -> Bool {
- var query: [CFString: Any] = [
- kSecClass: kSecClassGenericPassword,
- kSecAttrAccount: account.identifier,
- kSecAttrSynchronizable: account.synchronizable,
- kSecUseDataProtectionKeychain: true,
- kSecMatchLimit: kSecMatchLimitOne,
- kSecReturnData: false
- ]
-
- let context = LAContext()
- context.interactionNotAllowed = true
-
- query[kSecAttrService] = service?.identifier
- query[kSecAttrAccessGroup] = service?.accessGroup
- query[kSecUseAuthenticationContext] = context
-
- switch SecItemCopyMatching(query as CFDictionary, nil) {
- case errSecSuccess, errSecAuthFailed, errSecInteractionNotAllowed:
- return true
- case errSecItemNotFound:
- return false
- case let status:
- throw .osStatus(status)
- }
- }
-}
diff --git a/Sources/KeychainKit/Enums/KeychainError.swift b/Sources/KeychainKit/Enums/KeychainError.swift
deleted file mode 100644
index aa48d18..0000000
--- a/Sources/KeychainKit/Enums/KeychainError.swift
+++ /dev/null
@@ -1,44 +0,0 @@
-import Foundation
-
-/// An error that represents a keychain operation failure.
-///
-/// Each case corresponds to a specific system or data error encountered while performing keychain
-/// operations.
-public enum KeychainError: Error, Equatable {
- /// Authentication was required but failed or was canceled.
- case authenticationFailed
-
- /// An item with the same key already exists in the keychain.
- case duplicateItem
-
- /// The stored or retrieved data has an invalid format.
- case invalidData
-
- /// An unexpected system status code was returned.
- ///
- /// - Parameter status: The underlying `OSStatus` value.
- case osStatus(OSStatus)
-
- /// A lower-level error occurred during encoding, decoding, or other processing.
- ///
- /// - Parameter error: The underlying Foundation error, if available.
- case underlying(NSError?)
-
- /// A localized, human-readable description of the error.
- public var localizedDescription: String {
- switch self {
- case .authenticationFailed:
- return .Error.authenticationFailed
- case .duplicateItem:
- return .Error.duplicateItem
- case .invalidData:
- return .Error.invalidData
- case .osStatus(let status):
- let message = SecCopyErrorMessageString(status, nil)
- return .Error.osStatus(message as? String ?? "")
- case .underlying(let error):
- let message = error?.localizedDescription
- return .Error.underlying(message ?? "")
- }
- }
-}
diff --git a/Sources/KeychainKit/Extensions/String+Error.swift b/Sources/KeychainKit/Extensions/String+Error.swift
deleted file mode 100644
index 687d7db..0000000
--- a/Sources/KeychainKit/Extensions/String+Error.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-import Foundation
-import Localizable
-
-extension String {
- @Localizable(bundle: .module)
- enum Error {
- private enum Strings {
- case authenticationFailed
- case duplicateItem
- case invalidData
- case osStatus(String)
- case underlying(String)
- }
- }
-}
diff --git a/Sources/KeychainKit/Keychain.swift b/Sources/KeychainKit/Keychain.swift
new file mode 100644
index 0000000..5df6fea
--- /dev/null
+++ b/Sources/KeychainKit/Keychain.swift
@@ -0,0 +1,117 @@
+//
+// Keychain.swift
+// keychain-kit
+//
+// Created by Aleksey Zgurskiy on 02.02.2020.
+// Copyright © 2020 mr.noone. All rights reserved.
+//
+
+import Foundation
+
+public protocol KeychainProtocol {
+ func get(_ key: String) throws -> Data
+ func get(_ key: String) throws -> String
+ func get(_ key: String) throws -> UUID
+ func get(_ key: String, decoder: JSONDecoder) throws -> T where T: Decodable
+
+ func set(_ data: Data, for key: String) throws
+ func set(_ value: String, for key: String) throws
+ func set(_ uuid: UUID, for key: String) throws
+ func set(_ value: T, for key: String, encoder: JSONEncoder) throws where T: Encodable
+
+ func delete(_ key: String) throws
+}
+
+public struct Keychain: KeychainProtocol {
+ public enum Error: Swift.Error {
+ case noData
+ case unexpectedData
+ case unexpected(code: OSStatus)
+ }
+
+ // MARK: - Inits
+
+ public init() {}
+
+ // MARK: - Public methods
+
+ public func get(_ key: String) throws -> Data {
+ let query: [CFString : AnyObject] = [
+ kSecClass : kSecClassGenericPassword,
+ kSecAttrAccount : key as AnyObject,
+ kSecMatchLimit : kSecMatchLimitOne,
+ kSecReturnAttributes : kCFBooleanTrue,
+ kSecReturnData : kCFBooleanTrue
+ ]
+
+ var queryResult: AnyObject?
+ let status = withUnsafeMutablePointer(to: &queryResult) {
+ SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
+ }
+
+ guard status != errSecItemNotFound else { throw Error.noData }
+ guard status == noErr else { throw Error.unexpected(code: status) }
+
+ guard
+ let item = queryResult as? [CFString : AnyObject],
+ let data = item[kSecValueData] as? Data
+ else { throw Error.noData }
+
+ return data
+ }
+
+ public func get(_ key: String) throws -> String {
+ guard let value = String(data: try get(key), encoding: .utf8) else {
+ throw Error.unexpectedData
+ }
+ return value
+ }
+
+ public func get(_ key: String) throws -> UUID {
+ guard let value = UUID(uuidString: try get(key)) else {
+ throw Error.unexpectedData
+ }
+ return value
+ }
+
+ public func get(_ key: String, decoder: JSONDecoder = JSONDecoder()) throws -> T where T: Decodable {
+ return try decoder.decode(T.self, from: get(key))
+ }
+
+ public func set(_ data: Data, for key: String) throws {
+ try delete(key)
+
+ let query: [CFString : AnyObject] = [
+ kSecClass : kSecClassGenericPassword,
+ kSecAttrAccount : key as AnyObject,
+ kSecValueData : data as AnyObject
+ ]
+
+ let status = SecItemAdd(query as CFDictionary, nil)
+ guard status == noErr else { throw Error.unexpected(code: status) }
+ }
+
+ public func set(_ value: String, for key: String) throws {
+ try set(value.data(using: .utf8)!, for: key)
+ }
+
+ public func set(_ uuid: UUID, for key: String) throws {
+ try set(uuid.uuidString, for: key)
+ }
+
+ public func set(_ value: T, for key: String, encoder: JSONEncoder = JSONEncoder()) throws where T: Encodable {
+ try set(encoder.encode(value), for: key)
+ }
+
+ public func delete(_ key: String) throws {
+ let query: [CFString : AnyObject] = [
+ kSecClass : kSecClassGenericPassword,
+ kSecAttrAccount : key as AnyObject
+ ]
+
+ let status = SecItemDelete(query as CFDictionary)
+ guard status == noErr || status == errSecItemNotFound else {
+ throw Error.unexpected(code: status)
+ }
+ }
+}
diff --git a/Sources/KeychainKit/Protocols/KeychainAccountProtocol.swift b/Sources/KeychainKit/Protocols/KeychainAccountProtocol.swift
deleted file mode 100644
index 67b1da1..0000000
--- a/Sources/KeychainKit/Protocols/KeychainAccountProtocol.swift
+++ /dev/null
@@ -1,53 +0,0 @@
-import Foundation
-
-/// A type that describes a keychain account configuration for secure item storage and access.
-///
-/// Conforming types define metadata that determines how the keychain protects, authenticates, and
-/// optionally synchronizes specific items.
-///
-/// ## Topics
-///
-/// ### Properties
-///
-/// - ``identifier``
-/// - ``protection``
-/// - ``accessFlags``
-/// - ``synchronizable``
-public protocol KeychainAccountProtocol: Sendable {
- /// A unique string that identifies the keychain account.
- var identifier: String { get }
-
- /// The keychain data protection level assigned to the account.
- ///
- /// Defaults to `kSecAttrAccessibleAfterFirstUnlock`. You can override this to use another
- /// accessibility option, such as `kSecAttrAccessibleWhenUnlocked` or
- /// `kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly`.
- var protection: CFString { get }
-
- /// The access control flags defining additional authentication requirements.
- ///
- /// Defaults to an empty set (`[]`). Override this to enforce constraints like `.userPresence`,
- /// `.biometryAny`, or `.devicePasscode`.
- var accessFlags: SecAccessControlCreateFlags { get }
-
- /// Indicates whether the item is synchronized through iCloud Keychain.
- ///
- /// Defaults to `false`. Set this to `true` if the item should be available across all devices
- /// associated with the same iCloud account.
- var synchronizable: Bool { get }
-}
-
-public extension KeychainAccountProtocol {
- var protection: CFString { kSecAttrAccessibleAfterFirstUnlock }
-
- var accessFlags: SecAccessControlCreateFlags { [] }
-
- var synchronizable: Bool { false }
-}
-
-public extension KeychainAccountProtocol where Self: RawRepresentable, Self.RawValue == String {
- /// A unique string that identifies the keychain account.
- ///
- /// Derived from the instance’s raw string value.
- var identifier: String { rawValue }
-}
diff --git a/Sources/KeychainKit/Protocols/KeychainServiceProtocol.swift b/Sources/KeychainKit/Protocols/KeychainServiceProtocol.swift
deleted file mode 100644
index ab1af9a..0000000
--- a/Sources/KeychainKit/Protocols/KeychainServiceProtocol.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-import Foundation
-
-/// A type that describes a keychain service used to group and identify stored items.
-///
-/// Conforming types define a unique service identifier and may optionally specify an access group
-/// for sharing keychain data between multiple apps or extensions.
-///
-/// ## Topics
-///
-/// ### Properties
-///
-/// - ``identifier``
-/// - ``accessGroup``
-public protocol KeychainServiceProtocol: Sendable {
- /// A unique string that identifies the keychain service.
- var identifier: String { get }
-
- /// An optional keychain access group identifier that enables shared access between apps.
- ///
- /// Defaults to `nil`, meaning no access group is specified.
- var accessGroup: String? { get }
-}
-
-public extension KeychainServiceProtocol {
- var accessGroup: String? { nil }
-}
-
-public extension KeychainServiceProtocol where Self: RawRepresentable, Self.RawValue == String {
- /// A unique string that identifies the keychain service.
- ///
- /// Derived from the instance’s raw string value.
- var identifier: String { rawValue }
-}
diff --git a/Sources/KeychainKit/Protocols/KeychainStorageProtocol.swift b/Sources/KeychainKit/Protocols/KeychainStorageProtocol.swift
deleted file mode 100644
index bf22753..0000000
--- a/Sources/KeychainKit/Protocols/KeychainStorageProtocol.swift
+++ /dev/null
@@ -1,185 +0,0 @@
-import Foundation
-
-/// A type that provides access to data stored in the keychain.
-///
-/// Conforming types define how items are encoded, saved, and accessed securely, using account and
-/// service descriptors to identify individual entries.
-///
-/// ## Topics
-///
-/// ### Associated Types
-///
-/// - ``Account``
-/// - ``Service``
-///
-/// ### Instance Properties
-///
-/// - ``service``
-///
-/// ### Retrieving Items
-///
-/// - ``get(by:)->Data?``
-/// - ``get(by:)->String?``
-/// - ``get(by:)->UUID?``
-/// - ``get(by:decoder:)``
-///
-/// ### Inserting Items
-///
-/// - ``insert(_:by:)-(Data,_)``
-/// - ``insert(_:by:)-(String,_)``
-/// - ``insert(_:by:)-(UUID,_)``
-/// - ``insert(_:by:encoder:)``
-///
-/// ### Deleting Items
-///
-/// - ``delete(by:)``
-///
-/// ### Checking Existence
-///
-/// - ``exists(by:)``
-public protocol KeychainStorageProtocol: Sendable {
- // MARK: - Types
-
- /// A type that describes a keychain account used to identify stored items.
- associatedtype Account: KeychainAccountProtocol
-
- /// A type that describes a keychain service used to group stored items.
- associatedtype Service: KeychainServiceProtocol
-
- // MARK: - Properties
-
- /// The keychain service associated with this storage instance.
- var service: Service? { get }
-
- // MARK: - Methods
-
- /// Retrieves raw data for the given account.
- ///
- /// - Parameter account: The account descriptor identifying the stored item.
- /// - Returns: The stored data, or `nil` if no item exists.
- /// - Throws: ``KeychainError`` if the operation fails.
- func get(by account: Account) throws(KeychainError) -> Data?
-
- /// Inserts raw data for the given account.
- ///
- /// - Parameters:
- /// - value: The data to store.
- /// - account: The account descriptor identifying the target item.
- /// - Throws: ``KeychainError`` if the operation fails.
- func insert(_ value: Data, by account: Account) throws(KeychainError)
-
- /// Deletes the item for the given account.
- ///
- /// - Parameter account: The account descriptor identifying the item to remove.
- /// - Throws: ``KeychainError`` if the operation fails.
- func delete(by account: Account) throws(KeychainError)
-
- /// Checks whether an item exists for the given account.
- ///
- /// - Parameter account: The account descriptor identifying the stored item.
- /// - Returns: `true` if the item exists; otherwise, `false`.
- /// - Throws: ``KeychainError`` if the check fails.
- func exists(by account: Account) throws(KeychainError) -> Bool
-}
-
-// MARK: - Get Extension
-
-public extension KeychainStorageProtocol {
- /// Retrieves a UTF-8 string for the given account.
- ///
- /// - Parameter account: The account descriptor identifying the stored item.
- /// - Returns: The decoded string, or `nil` if no item exists.
- /// - Throws: ``KeychainError`` if retrieval fails.
- /// - Throws: ``KeychainError/invalidData`` if the stored data cannot be decoded as UTF-8.
- func get(by account: Account) throws(KeychainError) -> String? {
- guard let data = try get(by: account) else { return nil }
- guard let string = String(data: data, encoding: .utf8) else {
- throw .invalidData
- }
- return string
- }
-
- /// Retrieves a UUID for the given account.
- ///
- /// - Parameter account: The account descriptor identifying the stored item.
- /// - Returns: The decoded UUID, or `nil` if no item exists.
- /// - Throws: ``KeychainError`` if retrieval fails.
- /// - Throws: ``KeychainError/invalidData`` if the stored value is not a valid UUID string.
- func get(by account: Account) throws(KeychainError) -> UUID? {
- guard let string: String = try get(by: account) else { return nil }
- guard let uuid = UUID(uuidString: string) else {
- throw .invalidData
- }
- return uuid
- }
-
- /// Retrieves and decodes a `Decodable` value for the given account.
- ///
- /// - Parameters:
- /// - account: The account descriptor identifying the stored item.
- /// - decoder: The JSON decoder used to decode the stored data.
- /// - Returns: The decoded value, or `nil` if no item exists.
- /// - Throws: ``KeychainError`` if retrieval fails.
- /// - Throws: ``KeychainError/underlying(_:)`` if JSON decoding fails.
- func get(
- by account: Account,
- decoder: JSONDecoder = .init()
- ) throws(KeychainError) -> T? {
- guard let data = try get(by: account) else { return nil }
- do {
- return try decoder.decode(T.self, from: data)
- } catch {
- throw .underlying(error as NSError)
- }
- }
-}
-
-// MARK: - Set Extension
-
-public extension KeychainStorageProtocol {
- /// Inserts a UTF-8 string for the given account.
- ///
- /// - Parameters:
- /// - value: The string to store.
- /// - account: The account descriptor identifying the target item.
- /// - Throws: ``KeychainError`` if the operation fails.
- /// - Throws: ``KeychainError/invalidData`` if the string cannot be encoded as UTF-8.
- func insert(_ value: String, by account: Account) throws(KeychainError) {
- guard let data = value.data(using: .utf8) else {
- throw .invalidData
- }
- try insert(data, by: account)
- }
-
- /// Inserts a UUID for the given account.
- ///
- /// - Parameters:
- /// - value: The UUID to store.
- /// - account: The account descriptor identifying the target item.
- /// - Throws: ``KeychainError`` if the operation fails.
- func insert(_ value: UUID, by account: Account) throws(KeychainError) {
- try insert(value.uuidString, by: account)
- }
-
- /// Encodes and inserts an `Encodable` value for the given account.
- ///
- /// - Parameters:
- /// - value: The value to encode and store.
- /// - account: The account descriptor identifying the target item.
- /// - encoder: The JSON encoder used to encode the value.
- /// - Throws: ``KeychainError`` if the operation fails.
- /// - Throws: ``KeychainError/underlying(_:)`` if JSON encoding fails.
- func insert(
- _ value: T,
- by account: Account,
- encoder: JSONEncoder = .init()
- ) throws(KeychainError) {
- let data: Data
- do {
- data = try encoder.encode(value)
- } catch {
- throw .underlying(error as NSError)
- }
- try insert(data, by: account)
- }
-}
diff --git a/Sources/KeychainKit/Resources/Localizable.xcstrings b/Sources/KeychainKit/Resources/Localizable.xcstrings
deleted file mode 100644
index 44446a7..0000000
--- a/Sources/KeychainKit/Resources/Localizable.xcstrings
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "sourceLanguage" : "en",
- "strings" : {
- "Error.authenticationFailed" : {
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Authentication failed"
- }
- }
- }
- },
- "Error.duplicateItem" : {
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Item already exists"
- }
- }
- }
- },
- "Error.invalidData" : {
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Stored item contains invalid or unexpected data"
- }
- }
- }
- },
- "Error.osStatus %@" : {
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Unexpected Keychain status: %@"
- }
- }
- }
- },
- "Error.underlying %@" : {
- "localizations" : {
- "en" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Unexpected error while working with Keychain: %@"
- }
- }
- }
- }
- },
- "version" : "1.0"
-}
\ No newline at end of file