diff --git a/.gitignore b/.gitignore index 330d167..67356a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,10 @@ -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore +## General +.DS_Store +.swiftpm +.build/ -## User settings -xcuserdata/ - -## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) -*.xcscmblueprint -*.xccheckout - -## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) -build/ -DerivedData/ -*.moved-aside +## Various settings +Package.resolved *.pbxuser !default.pbxuser *.mode1v3 @@ -21,70 +13,5 @@ DerivedData/ !default.mode2v3 *.perspectivev3 !default.perspectivev3 - -## Obj-C/Swift specific -*.hmap - -## App packaging -*.ipa -*.dSYM.zip -*.dSYM - -## Playgrounds -timeline.xctimeline -playground.xcworkspace - -# Swift Package Manager -# -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. -# Packages/ -# Package.pins -# Package.resolved -# *.xcodeproj -# -# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata -# hence it is not needed unless you have added a package configuration file to your project -# .swiftpm - -.build/ - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -# Pods/ -# -# Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace - -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build/ - -# Accio dependency management -Dependencies/ -.accio/ - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. -# Instead, use fastlane to re-generate the screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots/**/*.png -fastlane/test_output - -# Code Injection -# -# After new code Injection tools there's a generated folder /iOSInjectionProject -# https://github.com/johnno1962/injectionforxcode - -iOSInjectionProject/ +xcuserdata/ +*.xcuserdatad/ diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..b77abe6 --- /dev/null +++ b/.swift-format @@ -0,0 +1,15 @@ +{ + "fileScopedDeclarationPrivacy": { + "accessLevel": "private" + }, + "indentBlankLines": true, + "indentation": { + "spaces": 4 + }, + "lineLength": 9999, + "maximumBlankLines": 1, + "multiElementCollectionTrailingCommas": false, + "rules": { + "FileScopedDeclarationPrivacy": true + } +} diff --git a/LICENSE b/LICENSE index 7365fe7..b033d92 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Aleksey Zgurskiy +Copyright (c) 2025 ANGD Dev 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 new file mode 100644 index 0000000..273919b --- /dev/null +++ b/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 6.0 +// 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") + ] + ) + ] +) diff --git a/README.md b/README.md index f8af6be..c73e91a 100644 --- a/README.md +++ b/README.md @@ -1 +1,55 @@ -# keychain-kit \ No newline at end of file +# 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. diff --git a/Sources/KeychainKit/Classes/KeychainStorage.swift b/Sources/KeychainKit/Classes/KeychainStorage.swift new file mode 100644 index 0000000..9788e58 --- /dev/null +++ b/Sources/KeychainKit/Classes/KeychainStorage.swift @@ -0,0 +1,193 @@ +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 new file mode 100644 index 0000000..aa48d18 --- /dev/null +++ b/Sources/KeychainKit/Enums/KeychainError.swift @@ -0,0 +1,44 @@ +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 new file mode 100644 index 0000000..687d7db --- /dev/null +++ b/Sources/KeychainKit/Extensions/String+Error.swift @@ -0,0 +1,15 @@ +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/Protocols/KeychainAccountProtocol.swift b/Sources/KeychainKit/Protocols/KeychainAccountProtocol.swift new file mode 100644 index 0000000..67b1da1 --- /dev/null +++ b/Sources/KeychainKit/Protocols/KeychainAccountProtocol.swift @@ -0,0 +1,53 @@ +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 new file mode 100644 index 0000000..ab1af9a --- /dev/null +++ b/Sources/KeychainKit/Protocols/KeychainServiceProtocol.swift @@ -0,0 +1,33 @@ +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 new file mode 100644 index 0000000..bf22753 --- /dev/null +++ b/Sources/KeychainKit/Protocols/KeychainStorageProtocol.swift @@ -0,0 +1,185 @@ +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 new file mode 100644 index 0000000..44446a7 --- /dev/null +++ b/Sources/KeychainKit/Resources/Localizable.xcstrings @@ -0,0 +1,56 @@ +{ + "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 diff --git a/keychain-kit.xcodeproj/project.pbxproj b/keychain-kit.xcodeproj/project.pbxproj deleted file mode 100644 index 9a70958..0000000 --- a/keychain-kit.xcodeproj/project.pbxproj +++ /dev/null @@ -1,362 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - -/* Begin PBXBuildFile section */ - 4B08E1FC23E73380003504E1 /* keychain_kit.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B08E1FA23E73380003504E1 /* keychain_kit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 4B08E20823E73CAF003504E1 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08E20723E73CAF003504E1 /* Keychain.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 4B08E1F723E73380003504E1 /* KeychainKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KeychainKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B08E1FA23E73380003504E1 /* keychain_kit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = keychain_kit.h; sourceTree = ""; }; - 4B08E1FB23E73380003504E1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 4B08E20323E733CD003504E1 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; - 4B08E20423E733F3003504E1 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - 4B08E20523E733F9003504E1 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 4B08E20723E73CAF003504E1 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 4B08E1F423E73380003504E1 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 4B08E1ED23E7337F003504E1 = { - isa = PBXGroup; - children = ( - 4B08E1F923E73380003504E1 /* keychain-kit */, - 4B08E1F823E73380003504E1 /* Products */, - ); - sourceTree = ""; - }; - 4B08E1F823E73380003504E1 /* Products */ = { - isa = PBXGroup; - children = ( - 4B08E1F723E73380003504E1 /* KeychainKit.framework */, - ); - name = Products; - sourceTree = ""; - }; - 4B08E1F923E73380003504E1 /* keychain-kit */ = { - isa = PBXGroup; - children = ( - 4B08E20623E73C92003504E1 /* Sources */, - 4B08E20223E733BF003504E1 /* Support Files */, - ); - path = "keychain-kit"; - sourceTree = ""; - }; - 4B08E20223E733BF003504E1 /* Support Files */ = { - isa = PBXGroup; - children = ( - 4B08E1FA23E73380003504E1 /* keychain_kit.h */, - 4B08E1FB23E73380003504E1 /* Info.plist */, - 4B08E20323E733CD003504E1 /* Config.xcconfig */, - 4B08E20423E733F3003504E1 /* Debug.xcconfig */, - 4B08E20523E733F9003504E1 /* Release.xcconfig */, - ); - path = "Support Files"; - sourceTree = ""; - }; - 4B08E20623E73C92003504E1 /* Sources */ = { - isa = PBXGroup; - children = ( - 4B08E20723E73CAF003504E1 /* Keychain.swift */, - ); - path = Sources; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 4B08E1F223E73380003504E1 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B08E1FC23E73380003504E1 /* keychain_kit.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 4B08E1F623E73380003504E1 /* keychain-kit */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4B08E1FF23E73380003504E1 /* Build configuration list for PBXNativeTarget "keychain-kit" */; - buildPhases = ( - 4B08E1F223E73380003504E1 /* Headers */, - 4B08E1F323E73380003504E1 /* Sources */, - 4B08E1F423E73380003504E1 /* Frameworks */, - 4B08E1F523E73380003504E1 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "keychain-kit"; - productName = "keychain-kit"; - productReference = 4B08E1F723E73380003504E1 /* KeychainKit.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 4B08E1EE23E73380003504E1 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1130; - ORGANIZATIONNAME = mr.noone; - TargetAttributes = { - 4B08E1F623E73380003504E1 = { - CreatedOnToolsVersion = 11.3.1; - LastSwiftMigration = 1130; - }; - }; - }; - buildConfigurationList = 4B08E1F123E73380003504E1 /* Build configuration list for PBXProject "keychain-kit" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 4B08E1ED23E7337F003504E1; - productRefGroup = 4B08E1F823E73380003504E1 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 4B08E1F623E73380003504E1 /* keychain-kit */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 4B08E1F523E73380003504E1 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 4B08E1F323E73380003504E1 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B08E20823E73CAF003504E1 /* Keychain.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 4B08E1FD23E73380003504E1 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B08E20423E733F3003504E1 /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 4B08E1FE23E73380003504E1 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4B08E20523E733F9003504E1 /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 4B08E20023E73380003504E1 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 84Z2AMFMF3; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.1.1; - PRODUCT_BUNDLE_IDENTIFIER = "com.mr-noone.keychain-kit"; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 4B08E20123E73380003504E1 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 84Z2AMFMF3; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.1.1; - PRODUCT_BUNDLE_IDENTIFIER = "com.mr-noone.keychain-kit"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 4B08E1F123E73380003504E1 /* Build configuration list for PBXProject "keychain-kit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B08E1FD23E73380003504E1 /* Debug */, - 4B08E1FE23E73380003504E1 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4B08E1FF23E73380003504E1 /* Build configuration list for PBXNativeTarget "keychain-kit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B08E20023E73380003504E1 /* Debug */, - 4B08E20123E73380003504E1 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 4B08E1EE23E73380003504E1 /* Project object */; -} diff --git a/keychain-kit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/keychain-kit.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 2cbedfd..0000000 --- a/keychain-kit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/keychain-kit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/keychain-kit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/keychain-kit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/keychain-kit/Sources/Keychain.swift b/keychain-kit/Sources/Keychain.swift deleted file mode 100644 index 4b2038b..0000000 --- a/keychain-kit/Sources/Keychain.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// Keychain.swift -// keychain-kit -// -// Created by Aleksey Zgurskiy on 02.02.2020. -// Copyright © 2020 mr.noone. All rights reserved. -// - -import Foundation - -public struct Keychain { - 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/keychain-kit/Support Files/Config.xcconfig b/keychain-kit/Support Files/Config.xcconfig deleted file mode 100644 index ae8eefd..0000000 --- a/keychain-kit/Support Files/Config.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -// -// Config.xcconfig -// keychain-kit -// -// Created by Aleksey Zgurskiy on 02.02.2020. -// Copyright © 2020 mr.noone. All rights reserved. -// - -// Configuration settings file format documentation can be found at: -// https://help.apple.com/xcode/#/dev745c5c974 - -INFOPLIST_FILE = keychain-kit/Support Files/Info.plist -PRODUCT_NAME = KeychainKit // $(TARGET_NAME:c99extidentifier) diff --git a/keychain-kit/Support Files/Debug.xcconfig b/keychain-kit/Support Files/Debug.xcconfig deleted file mode 100644 index a30dcfa..0000000 --- a/keychain-kit/Support Files/Debug.xcconfig +++ /dev/null @@ -1,12 +0,0 @@ -// -// Debug.xcconfig -// keychain-kit -// -// Created by Aleksey Zgurskiy on 02.02.2020. -// Copyright © 2020 mr.noone. All rights reserved. -// - -// Configuration settings file format documentation can be found at: -// https://help.apple.com/xcode/#/dev745c5c974 - -#include "Config.xcconfig" diff --git a/keychain-kit/Support Files/Info.plist b/keychain-kit/Support Files/Info.plist deleted file mode 100644 index c0701c6..0000000 --- a/keychain-kit/Support Files/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/keychain-kit/Support Files/Release.xcconfig b/keychain-kit/Support Files/Release.xcconfig deleted file mode 100644 index f8da04d..0000000 --- a/keychain-kit/Support Files/Release.xcconfig +++ /dev/null @@ -1,12 +0,0 @@ -// -// Release.xcconfig -// keychain-kit -// -// Created by Aleksey Zgurskiy on 02.02.2020. -// Copyright © 2020 mr.noone. All rights reserved. -// - -// Configuration settings file format documentation can be found at: -// https://help.apple.com/xcode/#/dev745c5c974 - -#include "Config.xcconfig" diff --git a/keychain-kit/Support Files/keychain_kit.h b/keychain-kit/Support Files/keychain_kit.h deleted file mode 100644 index bae651a..0000000 --- a/keychain-kit/Support Files/keychain_kit.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// keychain_kit.h -// keychain-kit -// -// Created by Aleksey Zgurskiy on 02.02.2020. -// Copyright © 2020 mr.noone. All rights reserved. -// - -#import - -//! Project version number for keychain_kit. -FOUNDATION_EXPORT double keychain_kitVersionNumber; - -//! Project version string for keychain_kit. -FOUNDATION_EXPORT const unsigned char keychain_kitVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - -