├── .gitignore ├── .travis.yml ├── Debug.entitlements ├── LICENSE ├── Package.swift ├── README.md ├── SwiftKeychainWrapper.podspec ├── SwiftKeychainWrapper.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── SwiftKeychainWrapper.xcscheme ├── SwiftKeychainWrapper ├── Info.plist ├── KeychainItemAccessibility.swift ├── KeychainWrapper.swift ├── KeychainWrapperSubscript.swift └── SwiftKeychainWrapper.h ├── SwiftKeychainWrapperTests ├── Info.plist ├── KeychainWrapperDefaultWrapperTests.swift ├── KeychainWrapperDeleteTests.swift ├── KeychainWrapperPrimitiveValueTests.swift ├── KeychainWrapperTests.swift └── TestObject.swift └── TestHostApp ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Debug.entitlements ├── Info.plist └── ViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | .swiftpm/ 20 | 21 | # CocoaPods 22 | # 23 | # We recommend against adding the Pods directory to your .gitignore. However 24 | # you should judge for yourself, the pros and cons are mentioned at: 25 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 26 | # 27 | # Pods/ 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.2 3 | 4 | script: 5 | - xcodebuild test -scheme SwiftKeychainWrapper -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone SE,OS=12.1" 6 | # - xcodebuild test -scheme SwiftKeychainWrapper -sdk appletvsimulator -destination "platform=tvOS Simulator,name=Apple TV,OS=12.1" 7 | -------------------------------------------------------------------------------- /Debug.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | get-task-allow 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jason Rendel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftKeychainWrapper", 7 | products: [ 8 | .library(name: "SwiftKeychainWrapper", targets: ["SwiftKeychainWrapper"]) 9 | ], 10 | targets: [ 11 | .target(name: "SwiftKeychainWrapper", path: "SwiftKeychainWrapper"), 12 | .testTarget(name: "SwiftKeychainWrapperTests", dependencies: ["SwiftKeychainWrapper"], path: "SwiftKeychainWrapperTests") 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :no_entry: [DEPRECATED] This library is not being kept up to date. This was created when Swift was first released as a simple way to work with the Keychain. But the approach was restricted and not suited to more advanced keychain usage. 2 | 3 | # SwiftKeychainWrapper 4 | 5 | A simple wrapper for the iOS / tvOS Keychain to allow you to use it in a similar fashion to User Defaults. Written in Swift. 6 | 7 | Provides singleton instance that is setup to work for most needs. Use `KeychainWrapper.standard` to access the singleton instance. 8 | 9 | If you need to customize the keychain access to use a custom identifier or access group, you can create your own instance instead of using the singleton instance. 10 | 11 | By default, the Keychain Wrapper saves data as a Generic Password type in the iOS Keychain. It saves items such that they can only be accessed when the app is unlocked and open. If you are not familiar with the iOS Keychain usage, this provides a safe default for using the keychain. 12 | 13 | Users that want to deviate from this default implementation, now can do so in version 2.0 and up. Each request to save/read a key value now allows you to specify the keychain accessibility for that key. 14 | 15 | ## General Usage 16 | 17 | Add a string value to keychain: 18 | ``` swift 19 | let saveSuccessful: Bool = KeychainWrapper.standard.set("Some String", forKey: "myKey") 20 | ``` 21 | 22 | Retrieve a string value from keychain: 23 | ``` swift 24 | let retrievedString: String? = KeychainWrapper.standard.string(forKey: "myKey") 25 | ``` 26 | 27 | Remove a string value from keychain: 28 | ``` swift 29 | let removeSuccessful: Bool = KeychainWrapper.standard.removeObject(forKey: "myKey") 30 | ``` 31 | 32 | ## Custom Instance 33 | 34 | When the Keychain Wrapper is used, all keys are linked to a common identifier for your app, called the service name. By default this uses your main bundle identifier. However, you may also change it, or store multiple items to the keychain under different identifiers. 35 | 36 | To share keychain items between your applications, you may specify an access group and use that same access group in each application. 37 | 38 | To set a custom service name identifier or access group, you may now create your own instance of the keychain wrapper as follows: 39 | 40 | ``` swift 41 | let uniqueServiceName = "customServiceName" 42 | let uniqueAccessGroup = "sharedAccessGroupName" 43 | let customKeychainWrapperInstance = KeychainWrapper(serviceName: uniqueServiceName, accessGroup: uniqueAccessGroup) 44 | ``` 45 | The custom instance can then be used in place of the shared instance or static accessors: 46 | 47 | ``` swift 48 | let saveSuccessful: Bool = customKeychainWrapperInstance.set("Some String", forKey: "myKey") 49 | 50 | let retrievedString: String? = customKeychainWrapperInstance.string(forKey: "myKey") 51 | 52 | let removeSuccessful: Bool = customKeychainWrapperInstance.removeObject(forKey: "myKey") 53 | ``` 54 | 55 | ## Subscript usage 56 | 57 | Keychain can also be accessed with subscript as it is in dictionary. Keys can be predefined and listed in one place for convenience. 58 | 59 | Firstly, let's define the key to use later. 60 | 61 | ``` swift 62 | extension KeychainWrapper.Key { 63 | static let myKey: KeychainWrapper.Key = "myKey" 64 | } 65 | ``` 66 | 67 | And now we can use this key as follows: 68 | 69 | ``` swift 70 | KeychainWrapper.standard[.myKey] = "some string" 71 | 72 | let myValue: String? = KeychainWrapper.standard[.myKey] 73 | 74 | KeychainWrapper.standard.remove(forKey: .myKey) 75 | 76 | ``` 77 | 78 | 79 | ## Accessibility Options 80 | 81 | By default, all items saved to keychain can only be accessed when the device is unlocked. To change this accessibility, an optional `withAccessibility` param can be set on all requests. The enum `KeychainItemAccessibilty` provides an easy way to select the accessibility level desired: 82 | 83 | ``` swift 84 | KeychainWrapper.standard.set("Some String", forKey: "myKey", withAccessibility: .AfterFirstUnlock) 85 | ``` 86 | 87 | ## Synchronizable Option 88 | 89 | By default, all items saved to keychain are not synchronizable, so they are not synced with the iCloud. To change this, an `isSynchronizable` bool param can be set on all requests. You need the item to be synchronized with the iCloud if you want to have it on all of your devices: 90 | 91 | ``` swift 92 | KeychainWrapper.standard.set("Some String", forKey: "myKey", isSynchronizable: true) 93 | ``` 94 | 95 | **Important:** You can't modify value for key if it was previously set with different accessibility option. Remove the value for key and set it with new accessibility option. (Otherwise the value will not change). 96 | For example: 97 | ``` swift 98 | KeychainWrapper.standard.set("String one", forKey: "myKey", withAccessibility: .AfterFirstUnlock) 99 | KeychainWrapper.standard.removeObject(forKey: "myKey") 100 | KeychainWrapper.standard.set("String two", forKey: "myKey", withAccessibility: .Always) 101 | ``` 102 | 103 | ## Installation 104 | 105 | #### CocoaPods 106 | You can use [CocoaPods](http://cocoapods.org/) to install SwiftKeychainWrapper by adding it to your `Podfile`: 107 | 108 | ``` ruby 109 | use_frameworks! 110 | platform :ios, '8.0' 111 | 112 | target 'target_name' do 113 | pod 'SwiftKeychainWrapper' 114 | end 115 | ``` 116 | 117 | To use the keychain wrapper in your app, import SwiftKeychainWrapper into the file(s) where you want to use it. 118 | 119 | ``` swift 120 | import SwiftKeychainWrapper 121 | ``` 122 | 123 | #### Carthage 124 | You can use [Carthage](https://github.com/Carthage/Carthage) to install SwiftKeychainWrapper by adding it to your `Cartfile`. 125 | 126 | Swift 3.0: 127 | ``` 128 | github "jrendel/SwiftKeychainWrapper" ~> 3.0 129 | ``` 130 | 131 | Swift 2.3: 132 | ``` 133 | github "jrendel/SwiftKeychainWrapper" == 2.1.1 134 | ``` 135 | 136 | #### Swift Package Manager 137 | You can use [Swift Package Manager](https://swift.org/package-manager/) to install SwiftKeychainWrapper using Xcode: 138 | 139 | 1. Open your project in Xcode 140 | 141 | 2. Click "File" -> "Swift Packages" -> "Add Package Dependency..." 142 | 143 | 3. Paste the following URL: https://github.com/jrendel/SwiftKeychainWrapper 144 | 145 | 4. Click "Next" -> "Next" -> "Finish" 146 | 147 | 148 | #### Manually 149 | Download and drop ```KeychainWrapper.swift``` and ```KeychainItemAcessibility.swift``` into your project. 150 | 151 | 152 | ## Release History 153 | 154 | * 4.1 155 | Added conditional logic for CGFloat accessories for when package is used where CGFloat is not available 156 | 157 | * 4.0 158 | Updated with SPM support and other community PRs. Minimum iOS version is now 9.0. 159 | 160 | * 3.4 161 | * Changed how Swift version is defined for CocoaPods 162 | 163 | * 3.3 164 | * Updates for Swift 5.0 and Xcode 10.2 165 | 166 | * 3.2 167 | * Updates for Swift 4.2 and Xcode 10 168 | 169 | * 3.1 170 | * Updates for Swift 3.1 171 | 172 | * 3.0.1 173 | * Added a host app for the unit tests to get around the issue with keychain access not working the same on iOS 10 simulators 174 | * Minor update to readme instructions 175 | 176 | * 3.0 177 | * Swift 3.0 update. Contains breaking API changes. 2.2.0 and 2.2.1 are now rolled into 3.0 178 | 179 | * 2.2.1 (Removed from Cocoapods) 180 | * Syntax updates to be more Swift 3 like 181 | 182 | * 2.2 (Removed from Cocoapods) 183 | * Updated to support Swift 3.0 184 | * Remove deprecated functions (static access) 185 | 186 | * 2.1 187 | * Updated to support Swift 2.3 188 | 189 | * 2.0 190 | * Further changes to more closely align the API with how `NSUserDefaults` works. Access to the default implementation is now done through a singleton instance. Static accessors have been included that wrap this shared instance to maintain backwards compatibility. These will be removed in the next update 191 | * Ability to change keychain service name identifier and access group on the shared instance has been deprecated. Users now have the ability to create their own instance of the keychain if they want to customize these. 192 | * Addtional options have been provided to alter the keychain accessibility for each key value saved. 193 | 194 | * 1.0.11 195 | * Update for Swift 2.0 196 | 197 | * 1.0.10 198 | * Update License info. Merged Pull Request with Carthage support. 199 | 200 | * 1.0.8 201 | * Update for Swift 1.2 202 | 203 | * 1.0.7 204 | * Determined that once provisioned correctly for access groups, using KeychainWrapper on the simulator with access groups works. So I removed the simulator related check and unit tests previously added. 205 | 206 | * 1.0.6 207 | * Support for Access Groups 208 | * SwiftKeychainWrapperExample has been updated to show usage with an Access Group: https://github.com/jrendel/SwiftKeychainWrapperExample 209 | 210 | * Access Groups do not work on the simulator. Apps that are built for the simulator aren't signed, so there's no keychain access group for the simulator to check. This means that all apps can see all keychain items when run on the simulator. Attempting to set an access group will result in a failure when attempting to Add or Update keychain items. Because of this, the Keychain Wrapper detects if it is being using on a simulator and will not set an access group property if one is set. This allows the Keychain Wrapper to still be used on the simulator for development of your app. To properly test Keychain Access Groups, you will need to test on a device. 211 | 212 | * 1.0.5 213 | * This version converts the project to a proper Swift Framework and adds a podspec file to be compatible with the latest CocoaPods pre-release, which now supports Swift. 214 | 215 | * To see an example of usage with CocoaPods, I've created the repo SwiftKeychainWrapperExample: https://github.com/jrendel/SwiftKeychainWrapperExample 216 | 217 | * 1.0.2 218 | * Updated for Xcode 6.1 219 | 220 | --- 221 | 222 | I've been using an Objective-C based wrapper in my own projects for the past couple years. The original library I wrote for myself was based on the following tutorial: 223 | 224 | http://www.raywenderlich.com/6475/basic-security-in-ios-5-tutorial-part-1 225 | 226 | This is a rewrite of that code in Swift. 227 | 228 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 229 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftKeychainWrapper' 3 | s.version = '4.0.1' 4 | s.summary = 'Wrapper for the iOS Keychain written in Swift.' 5 | s.description = <<-DESC 6 | A simple wrapper for the iOS Keychain to allow you to use it in a similar fashion to UserDefaults. Supports Access Groups. Written in Swift.' 7 | DESC 8 | s.module_name = "SwiftKeychainWrapper" 9 | s.homepage = 'https://github.com/jrendel/SwiftKeychainWrapper' 10 | s.license = 'MIT' 11 | s.authors = { 'Jason Rendel' => 'jason@jasonrendel.com' } 12 | s.ios.deployment_target = '9.0' 13 | s.tvos.deployment_target = '11.0' 14 | s.swift_version = ['4.2', '5.0'] 15 | s.source = { :git => 'https://github.com/jrendel/SwiftKeychainWrapper.git', :tag => s.version } 16 | s.source_files = 'SwiftKeychainWrapper/*.{h,swift}' 17 | end 18 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1AC6BBDA1CCD272C00DE429B /* KeychainItemAccessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC6BBD91CCD272C00DE429B /* KeychainItemAccessibility.swift */; }; 11 | 470A7D3B1CCE40EE001768EE /* KeychainWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 470A7D3A1CCE40EE001768EE /* KeychainWrapperTests.swift */; }; 12 | 477728B91CAF29E500905FE4 /* KeychainWrapperPrimitiveValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477728B81CAF29E500905FE4 /* KeychainWrapperPrimitiveValueTests.swift */; }; 13 | 47E429C91DCCE2FD002BE498 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E429C81DCCE2FD002BE498 /* AppDelegate.swift */; }; 14 | 47E429CB1DCCE2FD002BE498 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E429CA1DCCE2FD002BE498 /* ViewController.swift */; }; 15 | 47E429CE1DCCE2FD002BE498 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 47E429CC1DCCE2FD002BE498 /* Main.storyboard */; }; 16 | 47E429D01DCCE2FD002BE498 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 47E429CF1DCCE2FD002BE498 /* Assets.xcassets */; }; 17 | 47E429D31DCCE2FD002BE498 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 47E429D11DCCE2FD002BE498 /* LaunchScreen.storyboard */; }; 18 | 5E79BF2F246847600035CAC0 /* KeychainWrapperSubscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E79BF2E246847600035CAC0 /* KeychainWrapperSubscript.swift */; }; 19 | 96405CF41CA57F460080FF45 /* KeychainWrapperDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96405CF31CA57F460080FF45 /* KeychainWrapperDeleteTests.swift */; }; 20 | 966177401D597872000772C5 /* KeychainWrapperDefaultWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9661773F1D597872000772C5 /* KeychainWrapperDefaultWrapperTests.swift */; }; 21 | 966E4AB01B96693F00A8DCEB /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 966E4AAF1B96693F00A8DCEB /* LICENSE */; }; 22 | 9694C2B51A66097F005B3030 /* SwiftKeychainWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 9694C2B41A66097F005B3030 /* SwiftKeychainWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; 23 | 9694C2BB1A66097F005B3030 /* SwiftKeychainWrapper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9694C2B01A66097F005B3030 /* SwiftKeychainWrapper.framework */; }; 24 | 9694C2CD1A660F90005B3030 /* KeychainWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9694C2CC1A660F90005B3030 /* KeychainWrapper.swift */; }; 25 | 9694C2D11A660F9C005B3030 /* TestObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9694C2CF1A660F9C005B3030 /* TestObject.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | 47E429D91DCCE408002BE498 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 961101BC19D2581700E6A6E3 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 47E429C51DCCE2FD002BE498; 34 | remoteInfo = TestHostApp; 35 | }; 36 | 9694C2BC1A66097F005B3030 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = 961101BC19D2581700E6A6E3 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 9694C2AF1A66097F005B3030; 41 | remoteInfo = SwiftKeychainWrapper; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | 1AC6BBD91CCD272C00DE429B /* KeychainItemAccessibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainItemAccessibility.swift; sourceTree = ""; }; 47 | 470A7D3A1CCE40EE001768EE /* KeychainWrapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainWrapperTests.swift; sourceTree = ""; }; 48 | 477728B81CAF29E500905FE4 /* KeychainWrapperPrimitiveValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainWrapperPrimitiveValueTests.swift; sourceTree = ""; }; 49 | 47E429C61DCCE2FD002BE498 /* TestHostApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestHostApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 47E429C81DCCE2FD002BE498 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 51 | 47E429CA1DCCE2FD002BE498 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 52 | 47E429CD1DCCE2FD002BE498 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53 | 47E429CF1DCCE2FD002BE498 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | 47E429D21DCCE2FD002BE498 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | 47E429D41DCCE2FD002BE498 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 47E429D81DCCE31C002BE498 /* Debug.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Debug.entitlements; sourceTree = ""; }; 57 | 5E79BF2E246847600035CAC0 /* KeychainWrapperSubscript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainWrapperSubscript.swift; sourceTree = ""; }; 58 | 961101D419D26F2800E6A6E3 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 59 | 96405CF31CA57F460080FF45 /* KeychainWrapperDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainWrapperDeleteTests.swift; sourceTree = ""; }; 60 | 9661773F1D597872000772C5 /* KeychainWrapperDefaultWrapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainWrapperDefaultWrapperTests.swift; sourceTree = ""; }; 61 | 966E4AAF1B96693F00A8DCEB /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 62 | 9694C2B01A66097F005B3030 /* SwiftKeychainWrapper.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftKeychainWrapper.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 9694C2B31A66097F005B3030 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | 9694C2B41A66097F005B3030 /* SwiftKeychainWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftKeychainWrapper.h; sourceTree = ""; }; 65 | 9694C2BA1A66097F005B3030 /* SwiftKeychainWrapperTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftKeychainWrapperTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 9694C2C01A66097F005B3030 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | 9694C2CC1A660F90005B3030 /* KeychainWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainWrapper.swift; sourceTree = ""; }; 68 | 9694C2CF1A660F9C005B3030 /* TestObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestObject.swift; sourceTree = ""; }; 69 | /* End PBXFileReference section */ 70 | 71 | /* Begin PBXFrameworksBuildPhase section */ 72 | 47E429C31DCCE2FD002BE498 /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 9694C2AC1A66097F005B3030 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | 9694C2B71A66097F005B3030 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | 9694C2BB1A66097F005B3030 /* SwiftKeychainWrapper.framework in Frameworks */, 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | /* End PBXFrameworksBuildPhase section */ 95 | 96 | /* Begin PBXGroup section */ 97 | 47E429C71DCCE2FD002BE498 /* TestHostApp */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 47E429C81DCCE2FD002BE498 /* AppDelegate.swift */, 101 | 47E429CA1DCCE2FD002BE498 /* ViewController.swift */, 102 | 47E429CC1DCCE2FD002BE498 /* Main.storyboard */, 103 | 47E429CF1DCCE2FD002BE498 /* Assets.xcassets */, 104 | 47E429D11DCCE2FD002BE498 /* LaunchScreen.storyboard */, 105 | 47E429D41DCCE2FD002BE498 /* Info.plist */, 106 | 47E429D81DCCE31C002BE498 /* Debug.entitlements */, 107 | ); 108 | path = TestHostApp; 109 | sourceTree = ""; 110 | }; 111 | 961101BB19D2581700E6A6E3 = { 112 | isa = PBXGroup; 113 | children = ( 114 | 966E4AAF1B96693F00A8DCEB /* LICENSE */, 115 | 961101D419D26F2800E6A6E3 /* README.md */, 116 | 9694C2B11A66097F005B3030 /* SwiftKeychainWrapper */, 117 | 9694C2BE1A66097F005B3030 /* SwiftKeychainWrapperTests */, 118 | 47E429C71DCCE2FD002BE498 /* TestHostApp */, 119 | 961101C719D2583500E6A6E3 /* Products */, 120 | ); 121 | sourceTree = ""; 122 | }; 123 | 961101C719D2583500E6A6E3 /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 9694C2B01A66097F005B3030 /* SwiftKeychainWrapper.framework */, 127 | 9694C2BA1A66097F005B3030 /* SwiftKeychainWrapperTests.xctest */, 128 | 47E429C61DCCE2FD002BE498 /* TestHostApp.app */, 129 | ); 130 | name = Products; 131 | sourceTree = ""; 132 | }; 133 | 9694C2B11A66097F005B3030 /* SwiftKeychainWrapper */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 9694C2B41A66097F005B3030 /* SwiftKeychainWrapper.h */, 137 | 5E79BF2E246847600035CAC0 /* KeychainWrapperSubscript.swift */, 138 | 9694C2CC1A660F90005B3030 /* KeychainWrapper.swift */, 139 | 1AC6BBD91CCD272C00DE429B /* KeychainItemAccessibility.swift */, 140 | 9694C2B21A66097F005B3030 /* Supporting Files */, 141 | ); 142 | path = SwiftKeychainWrapper; 143 | sourceTree = ""; 144 | }; 145 | 9694C2B21A66097F005B3030 /* Supporting Files */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 9694C2B31A66097F005B3030 /* Info.plist */, 149 | ); 150 | name = "Supporting Files"; 151 | sourceTree = ""; 152 | }; 153 | 9694C2BE1A66097F005B3030 /* SwiftKeychainWrapperTests */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 9694C2CF1A660F9C005B3030 /* TestObject.swift */, 157 | 470A7D3A1CCE40EE001768EE /* KeychainWrapperTests.swift */, 158 | 9661773F1D597872000772C5 /* KeychainWrapperDefaultWrapperTests.swift */, 159 | 96405CF31CA57F460080FF45 /* KeychainWrapperDeleteTests.swift */, 160 | 477728B81CAF29E500905FE4 /* KeychainWrapperPrimitiveValueTests.swift */, 161 | 9694C2BF1A66097F005B3030 /* Supporting Files */, 162 | ); 163 | path = SwiftKeychainWrapperTests; 164 | sourceTree = ""; 165 | }; 166 | 9694C2BF1A66097F005B3030 /* Supporting Files */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 9694C2C01A66097F005B3030 /* Info.plist */, 170 | ); 171 | name = "Supporting Files"; 172 | sourceTree = ""; 173 | }; 174 | /* End PBXGroup section */ 175 | 176 | /* Begin PBXHeadersBuildPhase section */ 177 | 9694C2AD1A66097F005B3030 /* Headers */ = { 178 | isa = PBXHeadersBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 9694C2B51A66097F005B3030 /* SwiftKeychainWrapper.h in Headers */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXHeadersBuildPhase section */ 186 | 187 | /* Begin PBXNativeTarget section */ 188 | 47E429C51DCCE2FD002BE498 /* TestHostApp */ = { 189 | isa = PBXNativeTarget; 190 | buildConfigurationList = 47E429D71DCCE2FD002BE498 /* Build configuration list for PBXNativeTarget "TestHostApp" */; 191 | buildPhases = ( 192 | 47E429C21DCCE2FD002BE498 /* Sources */, 193 | 47E429C31DCCE2FD002BE498 /* Frameworks */, 194 | 47E429C41DCCE2FD002BE498 /* Resources */, 195 | ); 196 | buildRules = ( 197 | ); 198 | dependencies = ( 199 | ); 200 | name = TestHostApp; 201 | productName = TestHostApp; 202 | productReference = 47E429C61DCCE2FD002BE498 /* TestHostApp.app */; 203 | productType = "com.apple.product-type.application"; 204 | }; 205 | 9694C2AF1A66097F005B3030 /* SwiftKeychainWrapper */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = 9694C2C71A66097F005B3030 /* Build configuration list for PBXNativeTarget "SwiftKeychainWrapper" */; 208 | buildPhases = ( 209 | 9694C2AB1A66097F005B3030 /* Sources */, 210 | 9694C2AC1A66097F005B3030 /* Frameworks */, 211 | 9694C2AD1A66097F005B3030 /* Headers */, 212 | 9694C2AE1A66097F005B3030 /* Resources */, 213 | ); 214 | buildRules = ( 215 | ); 216 | dependencies = ( 217 | ); 218 | name = SwiftKeychainWrapper; 219 | productName = SwiftKeychainWrapper; 220 | productReference = 9694C2B01A66097F005B3030 /* SwiftKeychainWrapper.framework */; 221 | productType = "com.apple.product-type.framework"; 222 | }; 223 | 9694C2B91A66097F005B3030 /* SwiftKeychainWrapperTests */ = { 224 | isa = PBXNativeTarget; 225 | buildConfigurationList = 9694C2C81A66097F005B3030 /* Build configuration list for PBXNativeTarget "SwiftKeychainWrapperTests" */; 226 | buildPhases = ( 227 | 9694C2B61A66097F005B3030 /* Sources */, 228 | 9694C2B71A66097F005B3030 /* Frameworks */, 229 | 9694C2B81A66097F005B3030 /* Resources */, 230 | ); 231 | buildRules = ( 232 | ); 233 | dependencies = ( 234 | 9694C2BD1A66097F005B3030 /* PBXTargetDependency */, 235 | 47E429DA1DCCE408002BE498 /* PBXTargetDependency */, 236 | ); 237 | name = SwiftKeychainWrapperTests; 238 | productName = SwiftKeychainWrapperTests; 239 | productReference = 9694C2BA1A66097F005B3030 /* SwiftKeychainWrapperTests.xctest */; 240 | productType = "com.apple.product-type.bundle.unit-test"; 241 | }; 242 | /* End PBXNativeTarget section */ 243 | 244 | /* Begin PBXProject section */ 245 | 961101BC19D2581700E6A6E3 /* Project object */ = { 246 | isa = PBXProject; 247 | attributes = { 248 | LastSwiftMigration = 0700; 249 | LastSwiftUpdateCheck = 0810; 250 | LastUpgradeCheck = 1200; 251 | ORGANIZATIONNAME = "Jason Rendel"; 252 | TargetAttributes = { 253 | 47E429C51DCCE2FD002BE498 = { 254 | CreatedOnToolsVersion = 8.1; 255 | LastSwiftMigration = 1020; 256 | ProvisioningStyle = Automatic; 257 | }; 258 | 9694C2AF1A66097F005B3030 = { 259 | CreatedOnToolsVersion = 6.1.1; 260 | LastSwiftMigration = 1020; 261 | }; 262 | 9694C2B91A66097F005B3030 = { 263 | CreatedOnToolsVersion = 6.1.1; 264 | DevelopmentTeam = QDW4H7J3NE; 265 | LastSwiftMigration = 1020; 266 | TestTargetID = 47E429C51DCCE2FD002BE498; 267 | }; 268 | }; 269 | }; 270 | buildConfigurationList = 961101BF19D2581700E6A6E3 /* Build configuration list for PBXProject "SwiftKeychainWrapper" */; 271 | compatibilityVersion = "Xcode 3.2"; 272 | developmentRegion = en; 273 | hasScannedForEncodings = 0; 274 | knownRegions = ( 275 | en, 276 | Base, 277 | ); 278 | mainGroup = 961101BB19D2581700E6A6E3; 279 | productRefGroup = 961101C719D2583500E6A6E3 /* Products */; 280 | projectDirPath = ""; 281 | projectRoot = ""; 282 | targets = ( 283 | 9694C2AF1A66097F005B3030 /* SwiftKeychainWrapper */, 284 | 9694C2B91A66097F005B3030 /* SwiftKeychainWrapperTests */, 285 | 47E429C51DCCE2FD002BE498 /* TestHostApp */, 286 | ); 287 | }; 288 | /* End PBXProject section */ 289 | 290 | /* Begin PBXResourcesBuildPhase section */ 291 | 47E429C41DCCE2FD002BE498 /* Resources */ = { 292 | isa = PBXResourcesBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | 47E429D31DCCE2FD002BE498 /* LaunchScreen.storyboard in Resources */, 296 | 47E429D01DCCE2FD002BE498 /* Assets.xcassets in Resources */, 297 | 47E429CE1DCCE2FD002BE498 /* Main.storyboard in Resources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | 9694C2AE1A66097F005B3030 /* Resources */ = { 302 | isa = PBXResourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | 966E4AB01B96693F00A8DCEB /* LICENSE in Resources */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | 9694C2B81A66097F005B3030 /* Resources */ = { 310 | isa = PBXResourcesBuildPhase; 311 | buildActionMask = 2147483647; 312 | files = ( 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | }; 316 | /* End PBXResourcesBuildPhase section */ 317 | 318 | /* Begin PBXSourcesBuildPhase section */ 319 | 47E429C21DCCE2FD002BE498 /* Sources */ = { 320 | isa = PBXSourcesBuildPhase; 321 | buildActionMask = 2147483647; 322 | files = ( 323 | 47E429CB1DCCE2FD002BE498 /* ViewController.swift in Sources */, 324 | 47E429C91DCCE2FD002BE498 /* AppDelegate.swift in Sources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | 9694C2AB1A66097F005B3030 /* Sources */ = { 329 | isa = PBXSourcesBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | 5E79BF2F246847600035CAC0 /* KeychainWrapperSubscript.swift in Sources */, 333 | 9694C2CD1A660F90005B3030 /* KeychainWrapper.swift in Sources */, 334 | 1AC6BBDA1CCD272C00DE429B /* KeychainItemAccessibility.swift in Sources */, 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | 9694C2B61A66097F005B3030 /* Sources */ = { 339 | isa = PBXSourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | 9694C2D11A660F9C005B3030 /* TestObject.swift in Sources */, 343 | 96405CF41CA57F460080FF45 /* KeychainWrapperDeleteTests.swift in Sources */, 344 | 477728B91CAF29E500905FE4 /* KeychainWrapperPrimitiveValueTests.swift in Sources */, 345 | 966177401D597872000772C5 /* KeychainWrapperDefaultWrapperTests.swift in Sources */, 346 | 470A7D3B1CCE40EE001768EE /* KeychainWrapperTests.swift in Sources */, 347 | ); 348 | runOnlyForDeploymentPostprocessing = 0; 349 | }; 350 | /* End PBXSourcesBuildPhase section */ 351 | 352 | /* Begin PBXTargetDependency section */ 353 | 47E429DA1DCCE408002BE498 /* PBXTargetDependency */ = { 354 | isa = PBXTargetDependency; 355 | target = 47E429C51DCCE2FD002BE498 /* TestHostApp */; 356 | targetProxy = 47E429D91DCCE408002BE498 /* PBXContainerItemProxy */; 357 | }; 358 | 9694C2BD1A66097F005B3030 /* PBXTargetDependency */ = { 359 | isa = PBXTargetDependency; 360 | target = 9694C2AF1A66097F005B3030 /* SwiftKeychainWrapper */; 361 | targetProxy = 9694C2BC1A66097F005B3030 /* PBXContainerItemProxy */; 362 | }; 363 | /* End PBXTargetDependency section */ 364 | 365 | /* Begin PBXVariantGroup section */ 366 | 47E429CC1DCCE2FD002BE498 /* Main.storyboard */ = { 367 | isa = PBXVariantGroup; 368 | children = ( 369 | 47E429CD1DCCE2FD002BE498 /* Base */, 370 | ); 371 | name = Main.storyboard; 372 | sourceTree = ""; 373 | }; 374 | 47E429D11DCCE2FD002BE498 /* LaunchScreen.storyboard */ = { 375 | isa = PBXVariantGroup; 376 | children = ( 377 | 47E429D21DCCE2FD002BE498 /* Base */, 378 | ); 379 | name = LaunchScreen.storyboard; 380 | sourceTree = ""; 381 | }; 382 | /* End PBXVariantGroup section */ 383 | 384 | /* Begin XCBuildConfiguration section */ 385 | 47E429D51DCCE2FD002BE498 /* Debug */ = { 386 | isa = XCBuildConfiguration; 387 | buildSettings = { 388 | ALWAYS_SEARCH_USER_PATHS = NO; 389 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 390 | CLANG_ANALYZER_NONNULL = YES; 391 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 392 | CLANG_CXX_LIBRARY = "libc++"; 393 | CLANG_ENABLE_MODULES = YES; 394 | CLANG_ENABLE_OBJC_ARC = YES; 395 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 396 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 397 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 398 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 399 | CODE_SIGN_ENTITLEMENTS = Debug.entitlements; 400 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 401 | COPY_PHASE_STRIP = NO; 402 | DEBUG_INFORMATION_FORMAT = dwarf; 403 | DEVELOPMENT_TEAM = ""; 404 | GCC_C_LANGUAGE_STANDARD = gnu99; 405 | GCC_DYNAMIC_NO_PIC = NO; 406 | GCC_OPTIMIZATION_LEVEL = 0; 407 | GCC_PREPROCESSOR_DEFINITIONS = ( 408 | "DEBUG=1", 409 | "$(inherited)", 410 | ); 411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 412 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 413 | INFOPLIST_FILE = TestHostApp/Info.plist; 414 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 415 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 416 | MTL_ENABLE_DEBUG_INFO = YES; 417 | PRODUCT_BUNDLE_IDENTIFIER = com.jasonrendel.TestHostApp; 418 | PRODUCT_NAME = "$(TARGET_NAME)"; 419 | SDKROOT = iphoneos; 420 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 421 | TARGETED_DEVICE_FAMILY = "1,2"; 422 | }; 423 | name = Debug; 424 | }; 425 | 47E429D61DCCE2FD002BE498 /* Release */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | ALWAYS_SEARCH_USER_PATHS = NO; 429 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 430 | CLANG_ANALYZER_NONNULL = YES; 431 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 432 | CLANG_CXX_LIBRARY = "libc++"; 433 | CLANG_ENABLE_MODULES = YES; 434 | CLANG_ENABLE_OBJC_ARC = YES; 435 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 436 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 437 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 438 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 439 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 440 | COPY_PHASE_STRIP = NO; 441 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 442 | DEVELOPMENT_TEAM = ""; 443 | ENABLE_NS_ASSERTIONS = NO; 444 | GCC_C_LANGUAGE_STANDARD = gnu99; 445 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 446 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 447 | INFOPLIST_FILE = TestHostApp/Info.plist; 448 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 449 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 450 | MTL_ENABLE_DEBUG_INFO = NO; 451 | PRODUCT_BUNDLE_IDENTIFIER = com.jasonrendel.TestHostApp; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | SDKROOT = iphoneos; 454 | TARGETED_DEVICE_FAMILY = "1,2"; 455 | VALIDATE_PRODUCT = YES; 456 | }; 457 | name = Release; 458 | }; 459 | 961101C019D2581700E6A6E3 /* Debug */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | APPLICATION_EXTENSION_API_ONLY = YES; 463 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 464 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 465 | CLANG_WARN_BOOL_CONVERSION = YES; 466 | CLANG_WARN_COMMA = YES; 467 | CLANG_WARN_CONSTANT_CONVERSION = YES; 468 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 469 | CLANG_WARN_EMPTY_BODY = YES; 470 | CLANG_WARN_ENUM_CONVERSION = YES; 471 | CLANG_WARN_INFINITE_RECURSION = YES; 472 | CLANG_WARN_INT_CONVERSION = YES; 473 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 475 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 476 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 477 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 478 | CLANG_WARN_STRICT_PROTOTYPES = YES; 479 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 480 | CLANG_WARN_UNREACHABLE_CODE = YES; 481 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 482 | ENABLE_STRICT_OBJC_MSGSEND = YES; 483 | ENABLE_TESTABILITY = YES; 484 | GCC_NO_COMMON_BLOCKS = YES; 485 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 486 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 487 | GCC_WARN_UNDECLARED_SELECTOR = YES; 488 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 489 | GCC_WARN_UNUSED_FUNCTION = YES; 490 | GCC_WARN_UNUSED_VARIABLE = YES; 491 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 492 | ONLY_ACTIVE_ARCH = YES; 493 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 494 | SWIFT_VERSION = 5.0; 495 | }; 496 | name = Debug; 497 | }; 498 | 961101C119D2581700E6A6E3 /* Release */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | APPLICATION_EXTENSION_API_ONLY = YES; 502 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 503 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 504 | CLANG_WARN_BOOL_CONVERSION = YES; 505 | CLANG_WARN_COMMA = YES; 506 | CLANG_WARN_CONSTANT_CONVERSION = YES; 507 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 508 | CLANG_WARN_EMPTY_BODY = YES; 509 | CLANG_WARN_ENUM_CONVERSION = YES; 510 | CLANG_WARN_INFINITE_RECURSION = YES; 511 | CLANG_WARN_INT_CONVERSION = YES; 512 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 513 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 514 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 515 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 516 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 517 | CLANG_WARN_STRICT_PROTOTYPES = YES; 518 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 519 | CLANG_WARN_UNREACHABLE_CODE = YES; 520 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 521 | ENABLE_STRICT_OBJC_MSGSEND = YES; 522 | GCC_NO_COMMON_BLOCKS = YES; 523 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 524 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 525 | GCC_WARN_UNDECLARED_SELECTOR = YES; 526 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 527 | GCC_WARN_UNUSED_FUNCTION = YES; 528 | GCC_WARN_UNUSED_VARIABLE = YES; 529 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 530 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 531 | SWIFT_VERSION = 5.0; 532 | }; 533 | name = Release; 534 | }; 535 | 9694C2C31A66097F005B3030 /* Debug */ = { 536 | isa = XCBuildConfiguration; 537 | buildSettings = { 538 | ALWAYS_SEARCH_USER_PATHS = NO; 539 | APPLICATION_EXTENSION_API_ONLY = YES; 540 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 541 | CLANG_CXX_LIBRARY = "libc++"; 542 | CLANG_ENABLE_MODULES = YES; 543 | CLANG_ENABLE_OBJC_ARC = YES; 544 | CLANG_WARN_BOOL_CONVERSION = YES; 545 | CLANG_WARN_CONSTANT_CONVERSION = YES; 546 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 547 | CLANG_WARN_EMPTY_BODY = YES; 548 | CLANG_WARN_ENUM_CONVERSION = YES; 549 | CLANG_WARN_INT_CONVERSION = YES; 550 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 551 | CLANG_WARN_UNREACHABLE_CODE = YES; 552 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 553 | CODE_SIGN_IDENTITY = ""; 554 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 555 | COPY_PHASE_STRIP = NO; 556 | CURRENT_PROJECT_VERSION = 1; 557 | DEFINES_MODULE = YES; 558 | DYLIB_COMPATIBILITY_VERSION = 1; 559 | DYLIB_CURRENT_VERSION = 1; 560 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 561 | ENABLE_STRICT_OBJC_MSGSEND = YES; 562 | GCC_C_LANGUAGE_STANDARD = gnu99; 563 | GCC_DYNAMIC_NO_PIC = NO; 564 | GCC_OPTIMIZATION_LEVEL = 0; 565 | GCC_PREPROCESSOR_DEFINITIONS = ( 566 | "DEBUG=1", 567 | "$(inherited)", 568 | ); 569 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 570 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 571 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 572 | GCC_WARN_UNDECLARED_SELECTOR = YES; 573 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 574 | GCC_WARN_UNUSED_FUNCTION = YES; 575 | GCC_WARN_UNUSED_VARIABLE = YES; 576 | INFOPLIST_FILE = SwiftKeychainWrapper/Info.plist; 577 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 578 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 579 | MTL_ENABLE_DEBUG_INFO = YES; 580 | PRODUCT_BUNDLE_IDENTIFIER = "com.jasonrendel.$(PRODUCT_NAME:rfc1034identifier)"; 581 | PRODUCT_NAME = "$(TARGET_NAME)"; 582 | SDKROOT = iphoneos; 583 | SKIP_INSTALL = YES; 584 | TARGETED_DEVICE_FAMILY = "1,2"; 585 | VERSIONING_SYSTEM = "apple-generic"; 586 | VERSION_INFO_PREFIX = ""; 587 | }; 588 | name = Debug; 589 | }; 590 | 9694C2C41A66097F005B3030 /* Release */ = { 591 | isa = XCBuildConfiguration; 592 | buildSettings = { 593 | ALWAYS_SEARCH_USER_PATHS = NO; 594 | APPLICATION_EXTENSION_API_ONLY = YES; 595 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 596 | CLANG_CXX_LIBRARY = "libc++"; 597 | CLANG_ENABLE_MODULES = YES; 598 | CLANG_ENABLE_OBJC_ARC = YES; 599 | CLANG_WARN_BOOL_CONVERSION = YES; 600 | CLANG_WARN_CONSTANT_CONVERSION = YES; 601 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 602 | CLANG_WARN_EMPTY_BODY = YES; 603 | CLANG_WARN_ENUM_CONVERSION = YES; 604 | CLANG_WARN_INT_CONVERSION = YES; 605 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 606 | CLANG_WARN_UNREACHABLE_CODE = YES; 607 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 608 | CODE_SIGN_IDENTITY = ""; 609 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 610 | COPY_PHASE_STRIP = YES; 611 | CURRENT_PROJECT_VERSION = 1; 612 | DEFINES_MODULE = YES; 613 | DYLIB_COMPATIBILITY_VERSION = 1; 614 | DYLIB_CURRENT_VERSION = 1; 615 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 616 | ENABLE_NS_ASSERTIONS = NO; 617 | ENABLE_STRICT_OBJC_MSGSEND = YES; 618 | GCC_C_LANGUAGE_STANDARD = gnu99; 619 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 620 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 621 | GCC_WARN_UNDECLARED_SELECTOR = YES; 622 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 623 | GCC_WARN_UNUSED_FUNCTION = YES; 624 | GCC_WARN_UNUSED_VARIABLE = YES; 625 | INFOPLIST_FILE = SwiftKeychainWrapper/Info.plist; 626 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 627 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 628 | MTL_ENABLE_DEBUG_INFO = NO; 629 | PRODUCT_BUNDLE_IDENTIFIER = "com.jasonrendel.$(PRODUCT_NAME:rfc1034identifier)"; 630 | PRODUCT_NAME = "$(TARGET_NAME)"; 631 | SDKROOT = iphoneos; 632 | SKIP_INSTALL = YES; 633 | TARGETED_DEVICE_FAMILY = "1,2"; 634 | VALIDATE_PRODUCT = YES; 635 | VERSIONING_SYSTEM = "apple-generic"; 636 | VERSION_INFO_PREFIX = ""; 637 | }; 638 | name = Release; 639 | }; 640 | 9694C2C51A66097F005B3030 /* Debug */ = { 641 | isa = XCBuildConfiguration; 642 | buildSettings = { 643 | ALWAYS_SEARCH_USER_PATHS = NO; 644 | APPLICATION_EXTENSION_API_ONLY = NO; 645 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 646 | CLANG_CXX_LIBRARY = "libc++"; 647 | CLANG_ENABLE_MODULES = YES; 648 | CLANG_ENABLE_OBJC_ARC = YES; 649 | CLANG_WARN_BOOL_CONVERSION = YES; 650 | CLANG_WARN_CONSTANT_CONVERSION = YES; 651 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 652 | CLANG_WARN_EMPTY_BODY = YES; 653 | CLANG_WARN_ENUM_CONVERSION = YES; 654 | CLANG_WARN_INT_CONVERSION = YES; 655 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 656 | CLANG_WARN_UNREACHABLE_CODE = YES; 657 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 658 | CODE_SIGN_IDENTITY = "iPhone Developer"; 659 | COPY_PHASE_STRIP = NO; 660 | DEVELOPMENT_TEAM = QDW4H7J3NE; 661 | ENABLE_STRICT_OBJC_MSGSEND = YES; 662 | GCC_C_LANGUAGE_STANDARD = gnu99; 663 | GCC_DYNAMIC_NO_PIC = NO; 664 | GCC_OPTIMIZATION_LEVEL = 0; 665 | GCC_PREPROCESSOR_DEFINITIONS = ( 666 | "DEBUG=1", 667 | "$(inherited)", 668 | ); 669 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 670 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 671 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 672 | GCC_WARN_UNDECLARED_SELECTOR = YES; 673 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 674 | GCC_WARN_UNUSED_FUNCTION = YES; 675 | GCC_WARN_UNUSED_VARIABLE = YES; 676 | INFOPLIST_FILE = SwiftKeychainWrapperTests/Info.plist; 677 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 678 | MTL_ENABLE_DEBUG_INFO = YES; 679 | PRODUCT_BUNDLE_IDENTIFIER = "com.jasonrendel.$(PRODUCT_NAME:rfc1034identifier)"; 680 | PRODUCT_NAME = "$(TARGET_NAME)"; 681 | SDKROOT = iphoneos; 682 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestHostApp.app/TestHostApp"; 683 | }; 684 | name = Debug; 685 | }; 686 | 9694C2C61A66097F005B3030 /* Release */ = { 687 | isa = XCBuildConfiguration; 688 | buildSettings = { 689 | ALWAYS_SEARCH_USER_PATHS = NO; 690 | APPLICATION_EXTENSION_API_ONLY = NO; 691 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 692 | CLANG_CXX_LIBRARY = "libc++"; 693 | CLANG_ENABLE_MODULES = YES; 694 | CLANG_ENABLE_OBJC_ARC = YES; 695 | CLANG_WARN_BOOL_CONVERSION = YES; 696 | CLANG_WARN_CONSTANT_CONVERSION = YES; 697 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 698 | CLANG_WARN_EMPTY_BODY = YES; 699 | CLANG_WARN_ENUM_CONVERSION = YES; 700 | CLANG_WARN_INT_CONVERSION = YES; 701 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 702 | CLANG_WARN_UNREACHABLE_CODE = YES; 703 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 704 | CODE_SIGN_IDENTITY = "iPhone Developer"; 705 | COPY_PHASE_STRIP = YES; 706 | DEVELOPMENT_TEAM = QDW4H7J3NE; 707 | ENABLE_NS_ASSERTIONS = NO; 708 | ENABLE_STRICT_OBJC_MSGSEND = YES; 709 | GCC_C_LANGUAGE_STANDARD = gnu99; 710 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 711 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 712 | GCC_WARN_UNDECLARED_SELECTOR = YES; 713 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 714 | GCC_WARN_UNUSED_FUNCTION = YES; 715 | GCC_WARN_UNUSED_VARIABLE = YES; 716 | INFOPLIST_FILE = SwiftKeychainWrapperTests/Info.plist; 717 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 718 | MTL_ENABLE_DEBUG_INFO = NO; 719 | PRODUCT_BUNDLE_IDENTIFIER = "com.jasonrendel.$(PRODUCT_NAME:rfc1034identifier)"; 720 | PRODUCT_NAME = "$(TARGET_NAME)"; 721 | SDKROOT = iphoneos; 722 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestHostApp.app/TestHostApp"; 723 | VALIDATE_PRODUCT = YES; 724 | }; 725 | name = Release; 726 | }; 727 | /* End XCBuildConfiguration section */ 728 | 729 | /* Begin XCConfigurationList section */ 730 | 47E429D71DCCE2FD002BE498 /* Build configuration list for PBXNativeTarget "TestHostApp" */ = { 731 | isa = XCConfigurationList; 732 | buildConfigurations = ( 733 | 47E429D51DCCE2FD002BE498 /* Debug */, 734 | 47E429D61DCCE2FD002BE498 /* Release */, 735 | ); 736 | defaultConfigurationIsVisible = 0; 737 | defaultConfigurationName = Release; 738 | }; 739 | 961101BF19D2581700E6A6E3 /* Build configuration list for PBXProject "SwiftKeychainWrapper" */ = { 740 | isa = XCConfigurationList; 741 | buildConfigurations = ( 742 | 961101C019D2581700E6A6E3 /* Debug */, 743 | 961101C119D2581700E6A6E3 /* Release */, 744 | ); 745 | defaultConfigurationIsVisible = 0; 746 | defaultConfigurationName = Release; 747 | }; 748 | 9694C2C71A66097F005B3030 /* Build configuration list for PBXNativeTarget "SwiftKeychainWrapper" */ = { 749 | isa = XCConfigurationList; 750 | buildConfigurations = ( 751 | 9694C2C31A66097F005B3030 /* Debug */, 752 | 9694C2C41A66097F005B3030 /* Release */, 753 | ); 754 | defaultConfigurationIsVisible = 0; 755 | defaultConfigurationName = Release; 756 | }; 757 | 9694C2C81A66097F005B3030 /* Build configuration list for PBXNativeTarget "SwiftKeychainWrapperTests" */ = { 758 | isa = XCConfigurationList; 759 | buildConfigurations = ( 760 | 9694C2C51A66097F005B3030 /* Debug */, 761 | 9694C2C61A66097F005B3030 /* Release */, 762 | ); 763 | defaultConfigurationIsVisible = 0; 764 | defaultConfigurationName = Release; 765 | }; 766 | /* End XCConfigurationList section */ 767 | }; 768 | rootObject = 961101BC19D2581700E6A6E3 /* Project object */; 769 | } 770 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/xcshareddata/xcschemes/SwiftKeychainWrapper.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 4.0.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 4.0.1 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper/KeychainItemAccessibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainOptions.swift 3 | // SwiftKeychainWrapper 4 | // 5 | // Created by James Blair on 4/24/16. 6 | // Copyright © 2016 Jason Rendel. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | protocol KeychainAttrRepresentable { 31 | var keychainAttrValue: CFString { get } 32 | } 33 | 34 | // MARK: - KeychainItemAccessibility 35 | public enum KeychainItemAccessibility { 36 | /** 37 | The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. 38 | 39 | After the first unlock, the data remains accessible until the next restart. This is recommended for items that need to be accessed by background applications. Items with this attribute migrate to a new device when using encrypted backups. 40 | */ 41 | @available(iOS 4, *) 42 | case afterFirstUnlock 43 | 44 | /** 45 | The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user. 46 | 47 | After the first unlock, the data remains accessible until the next restart. This is recommended for items that need to be accessed by background applications. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present. 48 | */ 49 | @available(iOS 4, *) 50 | case afterFirstUnlockThisDeviceOnly 51 | 52 | /** 53 | The data in the keychain item can always be accessed regardless of whether the device is locked. 54 | 55 | This is not recommended for application use. Items with this attribute migrate to a new device when using encrypted backups. 56 | */ 57 | @available(iOS 4, *) 58 | case always 59 | 60 | /** 61 | The data in the keychain can only be accessed when the device is unlocked. Only available if a passcode is set on the device. 62 | 63 | This is recommended for items that only need to be accessible while the application is in the foreground. Items with this attribute never migrate to a new device. After a backup is restored to a new device, these items are missing. No items can be stored in this class on devices without a passcode. Disabling the device passcode causes all items in this class to be deleted. 64 | */ 65 | @available(iOS 8, *) 66 | case whenPasscodeSetThisDeviceOnly 67 | 68 | /** 69 | The data in the keychain item can always be accessed regardless of whether the device is locked. 70 | 71 | This is not recommended for application use. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present. 72 | */ 73 | @available(iOS 4, *) 74 | case alwaysThisDeviceOnly 75 | 76 | /** 77 | The data in the keychain item can be accessed only while the device is unlocked by the user. 78 | 79 | This is recommended for items that need to be accessible only while the application is in the foreground. Items with this attribute migrate to a new device when using encrypted backups. 80 | 81 | This is the default value for keychain items added without explicitly setting an accessibility constant. 82 | */ 83 | @available(iOS 4, *) 84 | case whenUnlocked 85 | 86 | /** 87 | The data in the keychain item can be accessed only while the device is unlocked by the user. 88 | 89 | This is recommended for items that need to be accessible only while the application is in the foreground. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present. 90 | */ 91 | @available(iOS 4, *) 92 | case whenUnlockedThisDeviceOnly 93 | 94 | static func accessibilityForAttributeValue(_ keychainAttrValue: CFString) -> KeychainItemAccessibility? { 95 | for (key, value) in keychainItemAccessibilityLookup { 96 | if value == keychainAttrValue { 97 | return key 98 | } 99 | } 100 | 101 | return nil 102 | } 103 | } 104 | 105 | private let keychainItemAccessibilityLookup: [KeychainItemAccessibility:CFString] = { 106 | var lookup: [KeychainItemAccessibility:CFString] = [ 107 | .afterFirstUnlock: kSecAttrAccessibleAfterFirstUnlock, 108 | .afterFirstUnlockThisDeviceOnly: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, 109 | .always: kSecAttrAccessibleAlways, 110 | .whenPasscodeSetThisDeviceOnly: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, 111 | .alwaysThisDeviceOnly : kSecAttrAccessibleAlwaysThisDeviceOnly, 112 | .whenUnlocked: kSecAttrAccessibleWhenUnlocked, 113 | .whenUnlockedThisDeviceOnly: kSecAttrAccessibleWhenUnlockedThisDeviceOnly 114 | ] 115 | 116 | return lookup 117 | }() 118 | 119 | extension KeychainItemAccessibility : KeychainAttrRepresentable { 120 | internal var keychainAttrValue: CFString { 121 | return keychainItemAccessibilityLookup[self]! 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper/KeychainWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainWrapper.swift 3 | // KeychainWrapper 4 | // 5 | // Created by Jason Rendel on 9/23/14. 6 | // Copyright (c) 2014 Jason Rendel. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | 31 | private let SecMatchLimit: String! = kSecMatchLimit as String 32 | private let SecReturnData: String! = kSecReturnData as String 33 | private let SecReturnPersistentRef: String! = kSecReturnPersistentRef as String 34 | private let SecValueData: String! = kSecValueData as String 35 | private let SecAttrAccessible: String! = kSecAttrAccessible as String 36 | private let SecClass: String! = kSecClass as String 37 | private let SecAttrService: String! = kSecAttrService as String 38 | private let SecAttrGeneric: String! = kSecAttrGeneric as String 39 | private let SecAttrAccount: String! = kSecAttrAccount as String 40 | private let SecAttrAccessGroup: String! = kSecAttrAccessGroup as String 41 | private let SecReturnAttributes: String = kSecReturnAttributes as String 42 | private let SecAttrSynchronizable: String = kSecAttrSynchronizable as String 43 | 44 | /// KeychainWrapper is a class to help make Keychain access in Swift more straightforward. It is designed to make accessing the Keychain services more like using NSUserDefaults, which is much more familiar to people. 45 | open class KeychainWrapper { 46 | 47 | @available(*, deprecated, message: "KeychainWrapper.defaultKeychainWrapper is deprecated since version 2.2.1, use KeychainWrapper.standard instead") 48 | public static let defaultKeychainWrapper = KeychainWrapper.standard 49 | 50 | /// Default keychain wrapper access 51 | public static let standard = KeychainWrapper() 52 | 53 | /// ServiceName is used for the kSecAttrService property to uniquely identify this keychain accessor. If no service name is specified, KeychainWrapper will default to using the bundleIdentifier. 54 | private (set) public var serviceName: String 55 | 56 | /// AccessGroup is used for the kSecAttrAccessGroup property to identify which Keychain Access Group this entry belongs to. This allows you to use the KeychainWrapper with shared keychain access between different applications. 57 | private (set) public var accessGroup: String? 58 | 59 | private static let defaultServiceName: String = { 60 | return Bundle.main.bundleIdentifier ?? "SwiftKeychainWrapper" 61 | }() 62 | 63 | private convenience init() { 64 | self.init(serviceName: KeychainWrapper.defaultServiceName) 65 | } 66 | 67 | /// Create a custom instance of KeychainWrapper with a custom Service Name and optional custom access group. 68 | /// 69 | /// - parameter serviceName: The ServiceName for this instance. Used to uniquely identify all keys stored using this keychain wrapper instance. 70 | /// - parameter accessGroup: Optional unique AccessGroup for this instance. Use a matching AccessGroup between applications to allow shared keychain access. 71 | public init(serviceName: String, accessGroup: String? = nil) { 72 | self.serviceName = serviceName 73 | self.accessGroup = accessGroup 74 | } 75 | 76 | // MARK:- Public Methods 77 | 78 | /// Checks if keychain data exists for a specified key. 79 | /// 80 | /// - parameter forKey: The key to check for. 81 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 82 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 83 | /// - returns: True if a value exists for the key. False otherwise. 84 | open func hasValue(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 85 | if let _ = data(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) { 86 | return true 87 | } else { 88 | return false 89 | } 90 | } 91 | 92 | open func accessibilityOfKey(_ key: String) -> KeychainItemAccessibility? { 93 | var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key) 94 | 95 | // Remove accessibility attribute 96 | keychainQueryDictionary.removeValue(forKey: SecAttrAccessible) 97 | 98 | // Limit search results to one 99 | keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne 100 | 101 | // Specify we want SecAttrAccessible returned 102 | keychainQueryDictionary[SecReturnAttributes] = kCFBooleanTrue 103 | 104 | // Search 105 | var result: AnyObject? 106 | let status = SecItemCopyMatching(keychainQueryDictionary as CFDictionary, &result) 107 | 108 | guard status == noErr, let resultsDictionary = result as? [String:AnyObject], let accessibilityAttrValue = resultsDictionary[SecAttrAccessible] as? String else { 109 | return nil 110 | } 111 | 112 | return KeychainItemAccessibility.accessibilityForAttributeValue(accessibilityAttrValue as CFString) 113 | } 114 | 115 | /// Get the keys of all keychain entries matching the current ServiceName and AccessGroup if one is set. 116 | open func allKeys() -> Set { 117 | var keychainQueryDictionary: [String:Any] = [ 118 | SecClass: kSecClassGenericPassword, 119 | SecAttrService: serviceName, 120 | SecReturnAttributes: kCFBooleanTrue!, 121 | SecMatchLimit: kSecMatchLimitAll, 122 | ] 123 | 124 | if let accessGroup = self.accessGroup { 125 | keychainQueryDictionary[SecAttrAccessGroup] = accessGroup 126 | } 127 | 128 | var result: AnyObject? 129 | let status = SecItemCopyMatching(keychainQueryDictionary as CFDictionary, &result) 130 | 131 | guard status == errSecSuccess else { return [] } 132 | 133 | var keys = Set() 134 | if let results = result as? [[AnyHashable: Any]] { 135 | for attributes in results { 136 | if let accountData = attributes[SecAttrAccount] as? Data, 137 | let key = String(data: accountData, encoding: String.Encoding.utf8) { 138 | keys.insert(key) 139 | } else if let accountData = attributes[kSecAttrAccount] as? Data, 140 | let key = String(data: accountData, encoding: String.Encoding.utf8) { 141 | keys.insert(key) 142 | } 143 | } 144 | } 145 | return keys 146 | } 147 | 148 | // MARK: Public Getters 149 | 150 | open func integer(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Int? { 151 | guard let numberValue = object(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) as? NSNumber else { 152 | return nil 153 | } 154 | 155 | return numberValue.intValue 156 | } 157 | 158 | open func float(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Float? { 159 | guard let numberValue = object(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) as? NSNumber else { 160 | return nil 161 | } 162 | 163 | return numberValue.floatValue 164 | } 165 | 166 | open func double(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Double? { 167 | guard let numberValue = object(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) as? NSNumber else { 168 | return nil 169 | } 170 | 171 | return numberValue.doubleValue 172 | } 173 | 174 | open func bool(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool? { 175 | guard let numberValue = object(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) as? NSNumber else { 176 | return nil 177 | } 178 | 179 | return numberValue.boolValue 180 | } 181 | 182 | /// Returns a string value for a specified key. 183 | /// 184 | /// - parameter forKey: The key to lookup data for. 185 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 186 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 187 | /// - returns: The String associated with the key if it exists. If no data exists, or the data found cannot be encoded as a string, returns nil. 188 | open func string(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> String? { 189 | guard let keychainData = data(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) else { 190 | return nil 191 | } 192 | 193 | return String(data: keychainData, encoding: String.Encoding.utf8) as String? 194 | } 195 | 196 | /// Returns an object that conforms to NSCoding for a specified key. 197 | /// 198 | /// - parameter forKey: The key to lookup data for. 199 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 200 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 201 | /// - returns: The decoded object associated with the key if it exists. If no data exists, or the data found cannot be decoded, returns nil. 202 | open func object(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> NSCoding? { 203 | guard let keychainData = data(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) else { 204 | return nil 205 | } 206 | 207 | return NSKeyedUnarchiver.unarchiveObject(with: keychainData) as? NSCoding 208 | } 209 | 210 | 211 | /// Returns a Data object for a specified key. 212 | /// 213 | /// - parameter forKey: The key to lookup data for. 214 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 215 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 216 | /// - returns: The Data object associated with the key if it exists. If no data exists, returns nil. 217 | open func data(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Data? { 218 | var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 219 | 220 | // Limit search results to one 221 | keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne 222 | 223 | // Specify we want Data/CFData returned 224 | keychainQueryDictionary[SecReturnData] = kCFBooleanTrue 225 | 226 | // Search 227 | var result: AnyObject? 228 | let status = SecItemCopyMatching(keychainQueryDictionary as CFDictionary, &result) 229 | 230 | return status == noErr ? result as? Data : nil 231 | } 232 | 233 | 234 | /// Returns a persistent data reference object for a specified key. 235 | /// 236 | /// - parameter forKey: The key to lookup data for. 237 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 238 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 239 | /// - returns: The persistent data reference object associated with the key if it exists. If no data exists, returns nil. 240 | open func dataRef(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Data? { 241 | var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 242 | 243 | // Limit search results to one 244 | keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne 245 | 246 | // Specify we want persistent Data/CFData reference returned 247 | keychainQueryDictionary[SecReturnPersistentRef] = kCFBooleanTrue 248 | 249 | // Search 250 | var result: AnyObject? 251 | let status = SecItemCopyMatching(keychainQueryDictionary as CFDictionary, &result) 252 | 253 | return status == noErr ? result as? Data : nil 254 | } 255 | 256 | // MARK: Public Setters 257 | 258 | @discardableResult open func set(_ value: Int, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 259 | return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 260 | } 261 | 262 | @discardableResult open func set(_ value: Float, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 263 | return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 264 | } 265 | 266 | @discardableResult open func set(_ value: Double, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 267 | return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 268 | } 269 | 270 | @discardableResult open func set(_ value: Bool, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 271 | return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 272 | } 273 | 274 | /// Save a String value to the keychain associated with a specified key. If a String value already exists for the given key, the string will be overwritten with the new value. 275 | /// 276 | /// - parameter value: The String value to save. 277 | /// - parameter forKey: The key to save the String under. 278 | /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. 279 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 280 | /// - returns: True if the save was successful, false otherwise. 281 | @discardableResult open func set(_ value: String, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 282 | if let data = value.data(using: .utf8) { 283 | return set(data, forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 284 | } else { 285 | return false 286 | } 287 | } 288 | 289 | /// Save an NSCoding compliant object to the keychain associated with a specified key. If an object already exists for the given key, the object will be overwritten with the new value. 290 | /// 291 | /// - parameter value: The NSCoding compliant object to save. 292 | /// - parameter forKey: The key to save the object under. 293 | /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. 294 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 295 | /// - returns: True if the save was successful, false otherwise. 296 | @discardableResult open func set(_ value: NSCoding, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 297 | let data = NSKeyedArchiver.archivedData(withRootObject: value) 298 | 299 | return set(data, forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 300 | } 301 | 302 | /// Save a Data object to the keychain associated with a specified key. If data already exists for the given key, the data will be overwritten with the new value. 303 | /// 304 | /// - parameter value: The Data object to save. 305 | /// - parameter forKey: The key to save the object under. 306 | /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. 307 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 308 | /// - returns: True if the save was successful, false otherwise. 309 | @discardableResult open func set(_ value: Data, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 310 | var keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 311 | 312 | keychainQueryDictionary[SecValueData] = value 313 | 314 | if let accessibility = accessibility { 315 | keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue 316 | } else { 317 | // Assign default protection - Protect the keychain entry so it's only valid when the device is unlocked 318 | keychainQueryDictionary[SecAttrAccessible] = KeychainItemAccessibility.whenUnlocked.keychainAttrValue 319 | } 320 | 321 | let status: OSStatus = SecItemAdd(keychainQueryDictionary as CFDictionary, nil) 322 | 323 | if status == errSecSuccess { 324 | return true 325 | } else if status == errSecDuplicateItem { 326 | return update(value, forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 327 | } else { 328 | return false 329 | } 330 | } 331 | 332 | @available(*, deprecated, message: "remove is deprecated since version 2.2.1, use removeObject instead") 333 | @discardableResult open func remove(key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 334 | return removeObject(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 335 | } 336 | 337 | /// Remove an object associated with a specified key. If re-using a key but with a different accessibility, first remove the previous key value using removeObjectForKey(:withAccessibility) using the same accessibilty it was saved with. 338 | /// 339 | /// - parameter forKey: The key value to remove data for. 340 | /// - parameter withAccessibility: Optional accessibility level to use when looking up the keychain item. 341 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 342 | /// - returns: True if successful, false otherwise. 343 | @discardableResult open func removeObject(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 344 | let keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 345 | 346 | // Delete 347 | let status: OSStatus = SecItemDelete(keychainQueryDictionary as CFDictionary) 348 | 349 | if status == errSecSuccess { 350 | return true 351 | } else { 352 | return false 353 | } 354 | } 355 | 356 | /// Remove all keychain data added through KeychainWrapper. This will only delete items matching the currnt ServiceName and AccessGroup if one is set. 357 | @discardableResult open func removeAllKeys() -> Bool { 358 | // Setup dictionary to access keychain and specify we are using a generic password (rather than a certificate, internet password, etc) 359 | var keychainQueryDictionary: [String:Any] = [SecClass:kSecClassGenericPassword] 360 | 361 | // Uniquely identify this keychain accessor 362 | keychainQueryDictionary[SecAttrService] = serviceName 363 | 364 | // Set the keychain access group if defined 365 | if let accessGroup = self.accessGroup { 366 | keychainQueryDictionary[SecAttrAccessGroup] = accessGroup 367 | } 368 | 369 | let status: OSStatus = SecItemDelete(keychainQueryDictionary as CFDictionary) 370 | 371 | if status == errSecSuccess { 372 | return true 373 | } else { 374 | return false 375 | } 376 | } 377 | 378 | /// Remove all keychain data, including data not added through keychain wrapper. 379 | /// 380 | /// - Warning: This may remove custom keychain entries you did not add via SwiftKeychainWrapper. 381 | /// 382 | open class func wipeKeychain() { 383 | deleteKeychainSecClass(kSecClassGenericPassword) // Generic password items 384 | deleteKeychainSecClass(kSecClassInternetPassword) // Internet password items 385 | deleteKeychainSecClass(kSecClassCertificate) // Certificate items 386 | deleteKeychainSecClass(kSecClassKey) // Cryptographic key items 387 | deleteKeychainSecClass(kSecClassIdentity) // Identity items 388 | } 389 | 390 | // MARK:- Private Methods 391 | 392 | /// Remove all items for a given Keychain Item Class 393 | /// 394 | /// 395 | @discardableResult private class func deleteKeychainSecClass(_ secClass: AnyObject) -> Bool { 396 | let query = [SecClass: secClass] 397 | let status: OSStatus = SecItemDelete(query as CFDictionary) 398 | 399 | if status == errSecSuccess { 400 | return true 401 | } else { 402 | return false 403 | } 404 | } 405 | 406 | /// Update existing data associated with a specified key name. The existing data will be overwritten by the new data. 407 | private func update(_ value: Data, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> Bool { 408 | var keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility, isSynchronizable: isSynchronizable) 409 | let updateDictionary = [SecValueData:value] 410 | 411 | // on update, only set accessibility if passed in 412 | if let accessibility = accessibility { 413 | keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue 414 | } 415 | 416 | // Update 417 | let status: OSStatus = SecItemUpdate(keychainQueryDictionary as CFDictionary, updateDictionary as CFDictionary) 418 | 419 | if status == errSecSuccess { 420 | return true 421 | } else { 422 | return false 423 | } 424 | } 425 | 426 | /// Setup the keychain query dictionary used to access the keychain on iOS for a specified key name. Takes into account the Service Name and Access Group if one is set. 427 | /// 428 | /// - parameter forKey: The key this query is for 429 | /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. If none is provided, will default to .WhenUnlocked 430 | /// - parameter isSynchronizable: A bool that describes if the item should be synchronizable, to be synched with the iCloud. If none is provided, will default to false 431 | /// - returns: A dictionary with all the needed properties setup to access the keychain on iOS 432 | private func setupKeychainQueryDictionary(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil, isSynchronizable: Bool = false) -> [String:Any] { 433 | // Setup default access as generic password (rather than a certificate, internet password, etc) 434 | var keychainQueryDictionary: [String:Any] = [SecClass:kSecClassGenericPassword] 435 | 436 | // Uniquely identify this keychain accessor 437 | keychainQueryDictionary[SecAttrService] = serviceName 438 | 439 | // Only set accessibiilty if its passed in, we don't want to default it here in case the user didn't want it set 440 | if let accessibility = accessibility { 441 | keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue 442 | } 443 | 444 | // Set the keychain access group if defined 445 | if let accessGroup = self.accessGroup { 446 | keychainQueryDictionary[SecAttrAccessGroup] = accessGroup 447 | } 448 | 449 | // Uniquely identify the account who will be accessing the keychain 450 | let encodedIdentifier: Data? = key.data(using: String.Encoding.utf8) 451 | 452 | keychainQueryDictionary[SecAttrGeneric] = encodedIdentifier 453 | 454 | keychainQueryDictionary[SecAttrAccount] = encodedIdentifier 455 | 456 | keychainQueryDictionary[SecAttrSynchronizable] = isSynchronizable ? kCFBooleanTrue : kCFBooleanFalse 457 | 458 | return keychainQueryDictionary 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper/KeychainWrapperSubscript.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainWrapperSubscript.swift 3 | // SwiftKeychainWrapper 4 | // 5 | // Created by Vato Kostava on 5/10/20. 6 | // Copyright © 2020 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if canImport(CoreGraphics) 12 | import CoreGraphics 13 | #endif 14 | 15 | public extension KeychainWrapper { 16 | 17 | func remove(forKey key: Key) { 18 | removeObject(forKey: key.rawValue) 19 | } 20 | 21 | } 22 | 23 | public extension KeychainWrapper { 24 | 25 | subscript(key: Key) -> String? { 26 | get { return string(forKey: key) } 27 | set { 28 | guard let value = newValue else { return } 29 | set(value, forKey: key.rawValue) 30 | } 31 | } 32 | 33 | subscript(key: Key) -> Bool? { 34 | get { return bool(forKey: key) } 35 | set { 36 | guard let value = newValue else { return } 37 | set(value, forKey: key.rawValue) 38 | } 39 | } 40 | 41 | subscript(key: Key) -> Int? { 42 | get { return integer(forKey: key) } 43 | set { 44 | guard let value = newValue else { return } 45 | set(value, forKey: key.rawValue) 46 | } 47 | } 48 | 49 | subscript(key: Key) -> Double? { 50 | get { return double(forKey: key) } 51 | set { 52 | guard let value = newValue else { return } 53 | set(value, forKey: key.rawValue) 54 | } 55 | } 56 | 57 | subscript(key: Key) -> Float? { 58 | get { return float(forKey: key) } 59 | set { 60 | guard let value = newValue else { return } 61 | set(value, forKey: key.rawValue) 62 | } 63 | } 64 | 65 | #if canImport(CoreGraphics) 66 | subscript(key: Key) -> CGFloat? { 67 | get { return cgFloat(forKey: key) } 68 | set { 69 | guard let cgValue = newValue else { return } 70 | let value = Float(cgValue) 71 | set(value, forKey: key.rawValue) 72 | } 73 | } 74 | #endif 75 | 76 | subscript(key: Key) -> Data? { 77 | get { return data(forKey: key) } 78 | set { 79 | guard let value = newValue else { return } 80 | set(value, forKey: key.rawValue) 81 | } 82 | } 83 | 84 | } 85 | 86 | 87 | public extension KeychainWrapper { 88 | 89 | func data(forKey key: Key) -> Data? { 90 | if let value = data(forKey: key.rawValue) { 91 | return value 92 | } 93 | return nil 94 | } 95 | 96 | func bool(forKey key: Key) -> Bool? { 97 | if let value = bool(forKey: key.rawValue) { 98 | return value 99 | } 100 | 101 | return nil 102 | } 103 | 104 | func integer(forKey key: Key) -> Int? { 105 | if let value = integer(forKey: key.rawValue) { 106 | return value 107 | } 108 | 109 | return nil 110 | } 111 | 112 | func float(forKey key: Key) -> Float? { 113 | if let value = float(forKey: key.rawValue) { 114 | return value 115 | } 116 | 117 | return nil 118 | } 119 | 120 | #if canImport(CoreGraphics) 121 | func cgFloat(forKey key: Key) -> CGFloat? { 122 | if let value = float(forKey: key) { 123 | return CGFloat(value) 124 | } 125 | 126 | return nil 127 | } 128 | #endif 129 | 130 | func double(forKey key: Key) -> Double? { 131 | if let value = double(forKey: key.rawValue) { 132 | return value 133 | } 134 | 135 | return nil 136 | } 137 | 138 | func string(forKey key: Key) -> String? { 139 | if let value = string(forKey: key.rawValue) { 140 | return value 141 | } 142 | 143 | return nil 144 | } 145 | 146 | } 147 | 148 | 149 | public extension KeychainWrapper { 150 | 151 | struct Key: Hashable, RawRepresentable, ExpressibleByStringLiteral { 152 | 153 | public var rawValue: String 154 | 155 | public init(rawValue: String) { 156 | self.rawValue = rawValue 157 | } 158 | 159 | public init(stringLiteral value: String) { 160 | self.rawValue = value 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper/SwiftKeychainWrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftKeychainWrapper.h 3 | // SwiftKeychainWrapper 4 | // 5 | // Created by Jason Rendel on 1/13/15. 6 | // Copyright (c) 2014 Jason Rendel. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | #import 28 | 29 | //! Project version number for SwiftKeychainWrapper. 30 | FOUNDATION_EXPORT double SwiftKeychainWrapperVersionNumber; 31 | 32 | //! Project version string for SwiftKeychainWrapper. 33 | FOUNDATION_EXPORT const unsigned char SwiftKeychainWrapperVersionString[]; 34 | 35 | // In this header, you should import all the public headers of your framework using statements like #import 36 | 37 | 38 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0.8 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0.8 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperTests/KeychainWrapperDefaultWrapperTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainWrapperDefaultWrapperTests.swift 3 | // SwiftKeychainWrapper 4 | // 5 | // Created by Jason Rendel on 8/8/16. 6 | // Copyright © 2016 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SwiftKeychainWrapper 11 | 12 | class KeychainWrapperDefaultWrapperTests: XCTestCase { 13 | let testKey = "acessorTestKey" 14 | let testString = "This is a test" 15 | 16 | let testKey2 = "acessorTestKey2" 17 | let testString2 = "Test 2 String" 18 | 19 | override func setUp() { 20 | super.setUp() 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | } 23 | 24 | override func tearDown() { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | 27 | // clean up keychain 28 | KeychainWrapper.standard.removeObject(forKey: testKey) 29 | KeychainWrapper.standard.removeObject(forKey: testKey2) 30 | 31 | super.tearDown() 32 | } 33 | 34 | func testDefaultServiceName() { 35 | let bundleIdentifier = Bundle.main.bundleIdentifier 36 | if let bundleIdentifierString = bundleIdentifier { 37 | XCTAssertEqual(KeychainWrapper.standard.serviceName, bundleIdentifierString, "Service Name should be equal to the bundle identifier when it is accessible") 38 | } else { 39 | XCTAssertEqual(KeychainWrapper.standard.serviceName, "SwiftKeychainWrapper", "Service Name should be equal to SwiftKeychainWrapper when the bundle identifier is not accessible") 40 | } 41 | } 42 | 43 | func testDefaultAccessGroup() { 44 | XCTAssertNil(KeychainWrapper.standard.accessGroup, "Access Group should be nil when nothing is set") 45 | } 46 | 47 | func testHasValueForKey() { 48 | XCTAssertFalse(KeychainWrapper.standard.hasValue(forKey: testKey), "Keychain should not have a value for the test key") 49 | 50 | KeychainWrapper.standard.set(testString, forKey: testKey) 51 | 52 | XCTAssertTrue(KeychainWrapper.standard.hasValue(forKey: testKey), "Keychain should have a value for the test key after it is set") 53 | } 54 | 55 | func testRemoveObjectFromKeychain() { 56 | KeychainWrapper.standard.set(testString, forKey: testKey) 57 | 58 | XCTAssertTrue(KeychainWrapper.standard.hasValue(forKey: testKey), "Keychain should have a value for the test key after it is set") 59 | 60 | KeychainWrapper.standard.removeObject(forKey: testKey) 61 | 62 | XCTAssertFalse(KeychainWrapper.standard.hasValue(forKey: testKey), "Keychain should not have a value for the test key after it is removed") 63 | } 64 | 65 | func testStringSave() { 66 | let stringSaved = KeychainWrapper.standard.set(testString, forKey: testKey) 67 | 68 | XCTAssertTrue(stringSaved, "String did not save to Keychain") 69 | 70 | // clean up keychain 71 | KeychainWrapper.standard.removeObject(forKey: testKey) 72 | } 73 | 74 | func testStringRetrieval() { 75 | KeychainWrapper.standard.set(testString, forKey: testKey) 76 | 77 | if let retrievedString = KeychainWrapper.standard.string(forKey: testKey) { 78 | XCTAssertEqual(retrievedString, testString, "String retrieved for key should equal string saved for key") 79 | } else { 80 | XCTFail("String for Key not found") 81 | } 82 | } 83 | 84 | func testStringRetrievalWhenValueDoesNotExist() { 85 | let retrievedString = KeychainWrapper.standard.string(forKey: testKey) 86 | XCTAssertNil(retrievedString, "String for Key should not exist") 87 | } 88 | 89 | func testMultipleStringSave() { 90 | if !KeychainWrapper.standard.set(testString, forKey: testKey) { 91 | XCTFail("String for testKey did not save") 92 | } 93 | 94 | if !KeychainWrapper.standard.set(testString2, forKey: testKey2) { 95 | XCTFail("String for testKey2 did not save") 96 | } 97 | 98 | if let string1Retrieved = KeychainWrapper.standard.string(forKey: testKey) { 99 | XCTAssertEqual(string1Retrieved, testString, "String retrieved for testKey should match string saved to testKey") 100 | } else { 101 | XCTFail("String for testKey could not be retrieved") 102 | } 103 | 104 | if let string2Retrieved = KeychainWrapper.standard.string(forKey: testKey2) { 105 | XCTAssertEqual(string2Retrieved, testString2, "String retrieved for testKey2 should match string saved to testKey2") 106 | } else { 107 | XCTFail("String for testKey2 could not be retrieved") 108 | } 109 | } 110 | 111 | func testMultipleStringsSavedToSameKey() { 112 | 113 | if !KeychainWrapper.standard.set(testString, forKey: testKey) { 114 | XCTFail("String for testKey did not save") 115 | } 116 | 117 | if let string1Retrieved = KeychainWrapper.standard.string(forKey: testKey) { 118 | XCTAssertEqual(string1Retrieved, testString, "String retrieved for testKey after first save should match first string saved testKey") 119 | } else { 120 | XCTFail("String for testKey could not be retrieved") 121 | } 122 | 123 | if !KeychainWrapper.standard.set(testString2, forKey: testKey) { 124 | XCTFail("String for testKey did not update") 125 | } 126 | 127 | if let string2Retrieved = KeychainWrapper.standard.string(forKey: testKey) { 128 | XCTAssertEqual(string2Retrieved, testString2, "String retrieved for testKey after update should match second string saved to testKey") 129 | } else { 130 | XCTFail("String for testKey could not be retrieved after update") 131 | } 132 | } 133 | 134 | func testNSCodingObjectSave() { 135 | let myTestObject = TestObject() 136 | let objectSaved = KeychainWrapper.standard.set(myTestObject, forKey: testKey) 137 | 138 | XCTAssertTrue(objectSaved, "Object that implements NSCoding should save to Keychain") 139 | } 140 | 141 | func testNSCodingObjectRetrieval() { 142 | let testInt: Int = 9 143 | let myTestObject = TestObject() 144 | myTestObject.objectName = testString 145 | myTestObject.objectRating = testInt 146 | 147 | KeychainWrapper.standard.set(myTestObject, forKey: testKey) 148 | 149 | if let retrievedObject = KeychainWrapper.standard.object(forKey: testKey) as? TestObject{ 150 | XCTAssertEqual(retrievedObject.objectName, testString, "NSCoding compliant object retrieved for key should have objectName property equal to what it was stored with") 151 | XCTAssertEqual(retrievedObject.objectRating, testInt, "NSCoding compliant object retrieved for key should have objectRating property equal to what it was stored with") 152 | } else { 153 | XCTFail("Object for Key not found") 154 | } 155 | } 156 | 157 | func testNSCodingObjectRetrievalWhenValueDoesNotExist() { 158 | let retrievedObject = KeychainWrapper.standard.object(forKey: testKey) as? TestObject 159 | XCTAssertNil(retrievedObject, "Object for Key should not exist") 160 | } 161 | 162 | func testDataSave() { 163 | let testData = testString.data(using: String.Encoding.utf8) 164 | 165 | if let data = testData { 166 | let dataSaved = KeychainWrapper.standard.set(data, forKey: testKey) 167 | 168 | XCTAssertTrue(dataSaved, "Data did not save to Keychain") 169 | } else { 170 | XCTFail("Failed to create Data") 171 | } 172 | } 173 | 174 | func testDataRetrieval() { 175 | guard let testData = testString.data(using: String.Encoding.utf8) else { 176 | XCTFail("Failed to create Data") 177 | return 178 | } 179 | 180 | KeychainWrapper.standard.set(testData, forKey: testKey) 181 | 182 | guard let retrievedData = KeychainWrapper.standard.data(forKey: testKey) else { 183 | XCTFail("Data for Key not found") 184 | return 185 | } 186 | 187 | if KeychainWrapper.standard.dataRef(forKey: testKey) == nil { 188 | XCTFail("Data references for Key not found") 189 | } 190 | 191 | if let retrievedString = String(data: retrievedData, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) { 192 | XCTAssertEqual(retrievedString, testString, "String retrieved from data for key should equal string saved as data for key") 193 | } else { 194 | XCTFail("Output Data for key does not match input. ") 195 | } 196 | } 197 | 198 | func testDataRetrievalWhenValueDoesNotExist() { 199 | let retrievedData = KeychainWrapper.standard.data(forKey: testKey) 200 | XCTAssertNil(retrievedData, "Data for Key should not exist") 201 | 202 | let retrievedDataRef = KeychainWrapper.standard.dataRef(forKey: testKey) 203 | XCTAssertNil(retrievedDataRef, "Data ref for Key should not exist") 204 | } 205 | 206 | func testKeysEmpty() { 207 | let keys = KeychainWrapper.standard.allKeys() 208 | XCTAssertEqual(keys, [], "Empty keychain should not contain keys") 209 | } 210 | 211 | func testKeysOneKey() { 212 | let keySuccessfullySet = KeychainWrapper.standard.set(testString, forKey: testKey) 213 | 214 | XCTAssertTrue(keySuccessfullySet, "Setting value on Standard Keychain failed") 215 | 216 | let keys = KeychainWrapper.standard.allKeys() 217 | XCTAssertEqual(keys, [testKey], "Keychain should contain the inserted key") 218 | } 219 | 220 | func testKeysMultipleKeys() { 221 | let keySuccessfullySet = KeychainWrapper.standard.set(testString, forKey: testKey) 222 | XCTAssertTrue(keySuccessfullySet, "Setting value on Standard Keychain failed") 223 | 224 | let key2SuccessfullySet = KeychainWrapper.standard.set(testString2, forKey: testKey2) 225 | 226 | XCTAssertTrue(key2SuccessfullySet, "Setting 2nd value on Standard Keychain failed") 227 | 228 | let keys = KeychainWrapper.standard.allKeys() 229 | XCTAssertEqual(keys, [testKey, testKey2], "Keychain should contain the inserted keys") 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperTests/KeychainWrapperDeleteTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainWrapperDeleteTests.swift 3 | // SwiftKeychainWrapper 4 | // 5 | // Created by Jason Rendel on 3/25/16. 6 | // Copyright © 2016 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SwiftKeychainWrapper 11 | 12 | class KeychainWrapperDeleteTests: XCTestCase { 13 | let testKey = "deleteTestKey" 14 | let testString = "This is a test" 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | override func tearDown() { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | super.tearDown() 24 | } 25 | 26 | func testRemoveAllKeysDeletesSpecificKey() { 27 | // save a value we can test delete on 28 | let stringSaved = KeychainWrapper.standard.set(testString, forKey: testKey) 29 | 30 | XCTAssertTrue(stringSaved, "String did not save to Keychain") 31 | 32 | // delete all 33 | let removeSuccessful = KeychainWrapper.standard.removeAllKeys() 34 | 35 | XCTAssertTrue(removeSuccessful, "Failed to remove all Keys") 36 | 37 | // confirm our test value was deleted 38 | let retrievedValue = KeychainWrapper.standard.string(forKey: testKey) 39 | 40 | XCTAssertNil(retrievedValue, "Test value was not deleted") 41 | } 42 | 43 | func testWipeKeychainDeletesSpecificKey() { 44 | // save a value we can test delete on 45 | let stringSaved = KeychainWrapper.standard.set(testString, forKey: testKey) 46 | 47 | XCTAssertTrue(stringSaved, "String did not save to Keychain") 48 | 49 | // delete all 50 | KeychainWrapper.wipeKeychain() 51 | 52 | // confirm our test value was deleted 53 | let retrievedValue = KeychainWrapper.standard.string(forKey: testKey) 54 | 55 | XCTAssertNil(retrievedValue, "Test value was not deleted") 56 | 57 | // clean up keychain 58 | KeychainWrapper.standard.removeObject(forKey: testKey) 59 | } 60 | 61 | // func testRemoveAllKeysOnlyRemovesKeysForCurrentServiceName() { 62 | // 63 | // } 64 | // 65 | // func testRemoveAllKeysOnlyRemovesKeysForCurrentAccessGroup() { 66 | // 67 | // } 68 | } 69 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperTests/KeychainWrapperPrimitiveValueTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainWrapperPrimitiveValueTests.swift 3 | // SwiftKeychainWrapper 4 | // 5 | // Created by Jason Rendel on 4/1/16. 6 | // Copyright © 2016 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SwiftKeychainWrapper 11 | 12 | class KeychainWrapperPrimitiveValueTests: XCTestCase { 13 | let testKey = "primitiveValueTestKey" 14 | let testInteger: Int = 42 15 | let testBool: Bool = false 16 | let testFloat: Float = 5.25 17 | let testDouble: Double = 10.75 18 | 19 | override func setUp() { 20 | super.setUp() 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | } 23 | 24 | override func tearDown() { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | super.tearDown() 27 | } 28 | 29 | func testIntegerSave() { 30 | let valueSaved = KeychainWrapper.standard.set(testInteger, forKey: testKey) 31 | 32 | XCTAssertTrue(valueSaved, "Integer value did not save to Keychain") 33 | 34 | // clean up keychain 35 | KeychainWrapper.standard.removeObject(forKey: testKey) 36 | } 37 | 38 | func testIntegerRetrieval() { 39 | KeychainWrapper.standard.set(testInteger, forKey: testKey) 40 | 41 | if let retrievedValue = KeychainWrapper.standard.integer(forKey: testKey) { 42 | XCTAssertEqual(retrievedValue, testInteger, "Integer value retrieved for key should equal value saved for key") 43 | } else { 44 | XCTFail("Integer value for Key not found") 45 | } 46 | } 47 | 48 | func testBoolSave() { 49 | let valueSaved = KeychainWrapper.standard.set(testBool, forKey: testKey) 50 | 51 | XCTAssertTrue(valueSaved, "Bool value did not save to Keychain") 52 | 53 | // clean up keychain 54 | KeychainWrapper.standard.removeObject(forKey: testKey) 55 | } 56 | 57 | func testBoolRetrieval() { 58 | KeychainWrapper.standard.set(testBool, forKey: testKey) 59 | 60 | if let retrievedValue = KeychainWrapper.standard.bool(forKey: testKey) { 61 | XCTAssertEqual(retrievedValue, testBool, "Bool value retrieved for key should equal value saved for key") 62 | } else { 63 | XCTFail("Bool value for Key not found") 64 | } 65 | } 66 | 67 | func testFloatSave() { 68 | let valueSaved = KeychainWrapper.standard.set(testFloat, forKey: testKey) 69 | 70 | XCTAssertTrue(valueSaved, "Float value did not save to Keychain") 71 | 72 | // clean up keychain 73 | KeychainWrapper.standard.removeObject(forKey: testKey) 74 | } 75 | 76 | func testFloatRetrieval() { 77 | KeychainWrapper.standard.set(testFloat, forKey: testKey) 78 | 79 | if let retrievedValue = KeychainWrapper.standard.float(forKey: testKey) { 80 | XCTAssertEqual(retrievedValue, testFloat, "Float value retrieved for key should equal value saved for key") 81 | } else { 82 | XCTFail("Float value for Key not found") 83 | } 84 | } 85 | 86 | func testDoubleSave() { 87 | let valueSaved = KeychainWrapper.standard.set(testDouble, forKey: testKey) 88 | 89 | XCTAssertTrue(valueSaved, "Double value did not save to Keychain") 90 | 91 | // clean up keychain 92 | KeychainWrapper.standard.removeObject(forKey: testKey) 93 | } 94 | 95 | func testDoubleRetrieval() { 96 | KeychainWrapper.standard.set(testDouble, forKey: testKey) 97 | 98 | if let retrievedValue = KeychainWrapper.standard.double(forKey: testKey) { 99 | XCTAssertEqual(retrievedValue, testDouble, "Double value retrieved for key should equal value saved for key") 100 | } else { 101 | XCTFail("Double value for Key not found") 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperTests/KeychainWrapperTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainWrapperTests.swift 3 | // SwiftKeychainWrapper 4 | // 5 | // Created by Jason Rendel on 4/25/16. 6 | // Copyright © 2016 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SwiftKeychainWrapper 11 | 12 | class KeychainWrapperTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testCustomInstance() { 25 | let uniqueServiceName = UUID().uuidString 26 | let uniqueAccessGroup = UUID().uuidString 27 | let customKeychainWrapperInstance = KeychainWrapper(serviceName: uniqueServiceName, accessGroup: uniqueAccessGroup) 28 | 29 | XCTAssertNotEqual(customKeychainWrapperInstance.serviceName, KeychainWrapper.standard.serviceName, "Custom instance initialized with unique service name, should not match standard Service Name") 30 | XCTAssertNotEqual(customKeychainWrapperInstance.accessGroup, KeychainWrapper.standard.accessGroup, "Custom instance initialized with unique access group, should not match standard Access Group") 31 | } 32 | 33 | func testAccessibility() { 34 | let accessibilityOptions: [KeychainItemAccessibility] = [ 35 | .afterFirstUnlock, 36 | .afterFirstUnlockThisDeviceOnly, 37 | .always, 38 | .whenPasscodeSetThisDeviceOnly, 39 | .alwaysThisDeviceOnly, 40 | .whenUnlocked, 41 | .whenUnlockedThisDeviceOnly 42 | ] 43 | 44 | let key = "testKey" 45 | 46 | for accessibilityOption in accessibilityOptions { 47 | KeychainWrapper.standard.set("Test123", forKey: key, withAccessibility: accessibilityOption) 48 | 49 | let accessibilityForKey = KeychainWrapper.standard.accessibilityOfKey(key) 50 | 51 | let accessibilityDescription = String(describing: accessibilityForKey) 52 | 53 | XCTAssertEqual(accessibilityForKey, accessibilityOption, "Accessibility does not match. Expected: \(accessibilityOption) Found: \(accessibilityDescription)") 54 | 55 | // INFO: If re-using a key but with a different accessibility, first remove the previous key value using removeObjectForKey(:withAccessibility) using the same accessibilty it was saved with 56 | KeychainWrapper.standard.removeObject(forKey: key, withAccessibility: accessibilityOption) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperTests/TestObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // testObject.swift 3 | // KeychainWrapper 4 | // 5 | // Created by Jason Rendel on 9/23/14. 6 | // Copyright (c) 2014 Jason Rendel. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | class TestObject: NSObject, NSCoding { 31 | var objectName = "Name" 32 | var objectRating = 0 33 | 34 | override init() { } 35 | 36 | required init?(coder decoder: NSCoder) { 37 | if let name = decoder.decodeObject(forKey: "objectName") as? String { 38 | self.objectName = name 39 | } 40 | 41 | self.objectRating = decoder.decodeInteger(forKey: "objectRating") 42 | } 43 | 44 | func encode(with encoder: NSCoder) { 45 | encoder.encode(self.objectName, forKey: "objectName") 46 | encoder.encode(self.objectRating, forKey: "objectRating") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /TestHostApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TestHostApp 4 | // 5 | // Created by Jason Rendel on 11/4/16. 6 | // Copyright © 2016 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /TestHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /TestHostApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TestHostApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /TestHostApp/Debug.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | get-task-allow 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TestHostApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TestHostApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TestHostApp 4 | // 5 | // Created by Jason Rendel on 11/4/16. 6 | // Copyright © 2016 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | 24 | } 25 | 26 | --------------------------------------------------------------------------------