├── .swift-version ├── TestHostAppTvOS ├── Assets.xcassets │ ├── Contents.json │ ├── App Icon & Top Shelf Image.brandassets │ │ ├── App Icon.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Contents.json │ │ │ │ └── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Contents.json │ │ │ │ └── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ ├── Middle.imagestacklayer │ │ │ │ ├── Contents.json │ │ │ │ └── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── App Icon - App Store.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Contents.json │ │ │ │ └── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Contents.json │ │ │ │ └── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ ├── Middle.imagestacklayer │ │ │ │ ├── Contents.json │ │ │ │ └── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Top Shelf Image.imageset │ │ │ └── Contents.json │ │ ├── Top Shelf Image Wide.imageset │ │ │ └── Contents.json │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── ViewController.swift ├── Info.plist ├── AppDelegate.swift └── Base.lproj │ └── Main.storyboard ├── .travis.yml ├── SwiftKeychainWrapper.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── SwiftKeychainWrapperTests.xcscheme │ ├── SwiftKeychainWrapperOSX.xcscheme │ ├── SwiftKeychainWrapperTvOS.xcscheme │ ├── TestHostAppTvOS.xcscheme │ ├── TestHostAppOSX.xcscheme │ ├── SwiftKeychainWrapper.xcscheme │ └── TestHostApp.xcscheme ├── Debug.entitlements ├── TestHostApp ├── Debug.entitlements ├── ViewController.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard └── AppDelegate.swift ├── TestHostAppOSX ├── TestHostAppOSX.entitlements ├── ViewController.swift ├── AppDelegate.swift ├── Info.plist ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json └── Base.lproj │ └── Main.storyboard ├── .gitignore ├── SwiftKeychainWrapperOSX ├── SwiftKeychainWrapperOSX.h └── Info.plist ├── SwiftKeychainWrapperTvOS ├── SwiftKeychainWrapperTvOS.h └── Info.plist ├── TestHostAppOSXTests ├── Info.plist └── TestHostAppOSXTests.swift ├── TestHostAppTvOSTests ├── Info.plist └── TestHostAppTvOSTests.swift ├── SwiftKeychainWrapper.podspec ├── SwiftKeychainWrapperTests ├── Info.plist ├── TestObject.swift ├── KeychainWrapperDeleteTests.swift ├── KeychainWrapperTests.swift ├── KeychainWrapperPrimitiveValueTests.swift └── KeychainWrapperDefaultWrapperTests.swift ├── SwiftKeychainWrapper ├── Info.plist ├── SwiftKeychainWrapper.h ├── KeychainItemAccessibility.swift └── KeychainWrapper.swift ├── LICENSE └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9 3 | 4 | script: 5 | - xcodebuild test -scheme SwiftKeychainWrapper -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone SE,OS=11.0" -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Debug.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | get-task-allow 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TestHostApp/Debug.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | get-task-allow 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /TestHostAppOSX/TestHostAppOSX.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /.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 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /TestHostAppOSX/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TestHostAppOSX 4 | // 5 | // Created by Anton Bukarev on 30/11/2017. 6 | // Copyright © 2017 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ViewController: NSViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | override var representedObject: Any? { 20 | didSet { 21 | // Update the view, if already loaded. 22 | } 23 | } 24 | 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /TestHostAppOSX/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TestHostAppOSX 4 | // 5 | // Created by Anton Bukarev on 30/11/2017. 6 | // Copyright © 2017 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /TestHostAppTvOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TestHostAppTvOS 4 | // 5 | // Created by Anton Bukarev on 11/12/2017. 6 | // Copyright © 2017 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 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperOSX/SwiftKeychainWrapperOSX.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftKeychainWrapperOSX.h 3 | // SwiftKeychainWrapperOSX 4 | // 5 | // Created by Anton Bukarev on 30/11/2017. 6 | // Copyright © 2017 Jason Rendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwiftKeychainWrapperOSX. 12 | FOUNDATION_EXPORT double SwiftKeychainWrapperOSXVersionNumber; 13 | 14 | //! Project version string for SwiftKeychainWrapperOSX. 15 | FOUNDATION_EXPORT const unsigned char SwiftKeychainWrapperOSXVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperTvOS/SwiftKeychainWrapperTvOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftKeychainWrapperTvOS.h 3 | // SwiftKeychainWrapperTvOS 4 | // 5 | // Created by Anton Bukarev on 11/12/2017. 6 | // Copyright © 2017 Jason Rendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwiftKeychainWrapperTvOS. 12 | FOUNDATION_EXPORT double SwiftKeychainWrapperTvOSVersionNumber; 13 | 14 | //! Project version string for SwiftKeychainWrapperTvOS. 15 | FOUNDATION_EXPORT const unsigned char SwiftKeychainWrapperTvOSVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /TestHostAppOSXTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TestHostAppTvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftKeychainWrapper' 3 | s.version = '3.1.0' 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.osx.deployment_target = '10.10' 13 | s.tvos.deployment_target = '9.0' 14 | s.ios.deployment_target = '8.0' 15 | s.source = { :git => 'https://github.com/jrendel/SwiftKeychainWrapper.git', :tag => s.version } 16 | s.source_files = 'SwiftKeychainWrapper/*.{h,swift}' 17 | end 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /TestHostAppTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - App Store.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "2320x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image Wide.imageset", 19 | "role" : "top-shelf-image-wide" 20 | }, 21 | { 22 | "size" : "1920x720", 23 | "idiom" : "tv", 24 | "filename" : "Top Shelf Image.imageset", 25 | "role" : "top-shelf-image" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperTvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /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 | 3.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 3.1.0 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SwiftKeychainWrapperOSX/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2017 Jason Rendel. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TestHostAppTvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | UIMainStoryboardFile 24 | Main 25 | UIRequiredDeviceCapabilities 26 | 27 | arm64 28 | 29 | UIUserInterfaceStyle 30 | Automatic 31 | 32 | 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /TestHostAppOSXTests/TestHostAppOSXTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestHostAppOSXTests.swift 3 | // TestHostAppOSXTests 4 | // 5 | // Created by Anton Bukarev on 30/11/2017. 6 | // Copyright © 2017 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TestHostAppOSX 11 | 12 | class TestHostAppOSXTests: 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 testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /TestHostAppTvOSTests/TestHostAppTvOSTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestHostAppTvOSTests.swift 3 | // TestHostAppTvOSTests 4 | // 5 | // Created by Anton Bukarev on 11/12/2017. 6 | // Copyright © 2017 Jason Rendel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TestHostAppTvOS 11 | 12 | class TestHostAppTvOSTests: 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 testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /TestHostAppOSX/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 Jason Rendel. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /TestHostAppOSX/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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: [UIApplicationLaunchOptionsKey: 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 | -------------------------------------------------------------------------------- /TestHostAppTvOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TestHostAppTvOS 4 | // 5 | // Created by Anton Bukarev on 11/12/2017. 6 | // Copyright © 2017 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: [UIApplicationLaunchOptionsKey: 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 throttle down OpenGL ES frame rates. 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 | -------------------------------------------------------------------------------- /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 | 11 | #if os(OSX) 12 | import SwiftKeychainWrapperOSX 13 | #elseif os(iOS) 14 | import SwiftKeychainWrapper 15 | #elseif os(tvOS) 16 | import SwiftKeychainWrapperTvOS 17 | #endif 18 | 19 | class KeychainWrapperDeleteTests: XCTestCase { 20 | let testKey = "deleteTestKey" 21 | let testString = "This is a test" 22 | 23 | override func setUp() { 24 | super.setUp() 25 | // Put setup code here. This method is called before the invocation of each test method in the class. 26 | } 27 | 28 | override func tearDown() { 29 | // Put teardown code here. This method is called after the invocation of each test method in the class. 30 | super.tearDown() 31 | } 32 | 33 | func testRemoveAllKeysDeletesSpecificKey() { 34 | // save a value we can test delete on 35 | let stringSaved = KeychainWrapper.standard.set(testString, forKey: testKey) 36 | 37 | XCTAssertTrue(stringSaved, "String did not save to Keychain") 38 | 39 | // delete all 40 | let removeSuccessful = KeychainWrapper.standard.removeAllKeys() 41 | 42 | XCTAssertTrue(removeSuccessful, "Failed to remove all Keys") 43 | 44 | // confirm our test value was deleted 45 | let retrievedValue = KeychainWrapper.standard.string(forKey: testKey) 46 | 47 | XCTAssertNil(retrievedValue, "Test value was not deleted") 48 | } 49 | 50 | func testWipeKeychainDeletesSpecificKey() { 51 | // save a value we can test delete on 52 | let stringSaved = KeychainWrapper.standard.set(testString, forKey: testKey) 53 | 54 | XCTAssertTrue(stringSaved, "String did not save to Keychain") 55 | 56 | // delete all 57 | KeychainWrapper.wipeKeychain() 58 | 59 | // confirm our test value was deleted 60 | let retrievedValue = KeychainWrapper.standard.string(forKey: testKey) 61 | 62 | XCTAssertNil(retrievedValue, "Test value was not deleted") 63 | 64 | // clean up keychain 65 | KeychainWrapper.standard.removeObject(forKey: testKey) 66 | } 67 | 68 | func testRemoveAllKeysOnlyRemovesKeysForCurrentServiceName() { 69 | 70 | } 71 | 72 | func testRemoveAllKeysOnlyRemovesKeysForCurrentAccessGroup() { 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /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 | 11 | #if os(OSX) 12 | import SwiftKeychainWrapperOSX 13 | #elseif os(iOS) 14 | import SwiftKeychainWrapper 15 | #elseif os(tvOS) 16 | import SwiftKeychainWrapperTvOS 17 | #endif 18 | 19 | class KeychainWrapperTests: XCTestCase { 20 | 21 | override func setUp() { 22 | super.setUp() 23 | // Put setup code here. This method is called before the invocation of each test method in the class. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testCustomInstance() { 32 | let uniqueServiceName = UUID().uuidString 33 | let uniqueAccessGroup = UUID().uuidString 34 | let customKeychainWrapperInstance = KeychainWrapper(serviceName: uniqueServiceName, accessGroup: uniqueAccessGroup) 35 | 36 | XCTAssertNotEqual(customKeychainWrapperInstance.serviceName, KeychainWrapper.standard.serviceName, "Custom instance initialized with unique service name, should not match standard Service Name") 37 | XCTAssertNotEqual(customKeychainWrapperInstance.accessGroup, KeychainWrapper.standard.accessGroup, "Custom instance initialized with unique access group, should not match standard Access Group") 38 | } 39 | 40 | func testAccessibility() { 41 | let accessibilityOptions: [KeychainItemAccessibility] = [ 42 | .afterFirstUnlock, 43 | .afterFirstUnlockThisDeviceOnly, 44 | .always, 45 | .whenPasscodeSetThisDeviceOnly, 46 | .alwaysThisDeviceOnly, 47 | .whenUnlocked, 48 | .whenUnlockedThisDeviceOnly 49 | ] 50 | 51 | let key = "testKey" 52 | 53 | for accessibilityOption in accessibilityOptions { 54 | KeychainWrapper.standard.set("Test123", forKey: key, withAccessibility: accessibilityOption) 55 | 56 | let accessibilityForKey = KeychainWrapper.standard.accessibilityOfKey(key) 57 | 58 | let accessibilityDescription = String(describing: accessibilityForKey) 59 | 60 | XCTAssertEqual(accessibilityForKey, accessibilityOption, "Accessibility does not match. Expected: \(accessibilityOption) Found: \(accessibilityDescription)") 61 | 62 | // 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 63 | KeychainWrapper.standard.removeObject(forKey: key, withAccessibility: accessibilityOption) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/xcshareddata/xcschemes/SwiftKeychainWrapperTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 57 | 58 | 59 | 60 | 66 | 67 | 69 | 70 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/xcshareddata/xcschemes/SwiftKeychainWrapperOSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/xcshareddata/xcschemes/SwiftKeychainWrapperTvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /TestHostAppTvOS/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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 11 | #if os(OSX) 12 | import SwiftKeychainWrapperOSX 13 | #elseif os(iOS) 14 | import SwiftKeychainWrapper 15 | #elseif os(tvOS) 16 | import SwiftKeychainWrapperTvOS 17 | #endif 18 | 19 | class KeychainWrapperPrimitiveValueTests: XCTestCase { 20 | let testKey = "primitiveValueTestKey" 21 | let testInteger: Int = 42 22 | let testBool: Bool = false 23 | let testFloat: Float = 5.25 24 | let testDouble: Double = 10.75 25 | 26 | override func setUp() { 27 | super.setUp() 28 | // Put setup code here. This method is called before the invocation of each test method in the class. 29 | } 30 | 31 | override func tearDown() { 32 | // Put teardown code here. This method is called after the invocation of each test method in the class. 33 | super.tearDown() 34 | } 35 | 36 | func testIntegerSave() { 37 | let valueSaved = KeychainWrapper.standard.set(testInteger, forKey: testKey) 38 | 39 | XCTAssertTrue(valueSaved, "Integer value did not save to Keychain") 40 | 41 | // clean up keychain 42 | KeychainWrapper.standard.removeObject(forKey: testKey) 43 | } 44 | 45 | func testIntegerRetrieval() { 46 | KeychainWrapper.standard.set(testInteger, forKey: testKey) 47 | 48 | if let retrievedValue = KeychainWrapper.standard.integer(forKey: testKey) { 49 | XCTAssertEqual(retrievedValue, testInteger, "Integer value retrieved for key should equal value saved for key") 50 | } else { 51 | XCTFail("Integer value for Key not found") 52 | } 53 | } 54 | 55 | func testBoolSave() { 56 | let valueSaved = KeychainWrapper.standard.set(testBool, forKey: testKey) 57 | 58 | XCTAssertTrue(valueSaved, "Bool value did not save to Keychain") 59 | 60 | // clean up keychain 61 | KeychainWrapper.standard.removeObject(forKey: testKey) 62 | } 63 | 64 | func testBoolRetrieval() { 65 | KeychainWrapper.standard.set(testBool, forKey: testKey) 66 | 67 | if let retrievedValue = KeychainWrapper.standard.bool(forKey: testKey) { 68 | XCTAssertEqual(retrievedValue, testBool, "Bool value retrieved for key should equal value saved for key") 69 | } else { 70 | XCTFail("Bool value for Key not found") 71 | } 72 | } 73 | 74 | func testFloatSave() { 75 | let valueSaved = KeychainWrapper.standard.set(testFloat, forKey: testKey) 76 | 77 | XCTAssertTrue(valueSaved, "Float value did not save to Keychain") 78 | 79 | // clean up keychain 80 | KeychainWrapper.standard.removeObject(forKey: testKey) 81 | } 82 | 83 | func testFloatRetrieval() { 84 | KeychainWrapper.standard.set(testFloat, forKey: testKey) 85 | 86 | if let retrievedValue = KeychainWrapper.standard.float(forKey: testKey) { 87 | XCTAssertEqual(retrievedValue, testFloat, "Float value retrieved for key should equal value saved for key") 88 | } else { 89 | XCTFail("Float value for Key not found") 90 | } 91 | } 92 | 93 | func testDoubleSave() { 94 | let valueSaved = KeychainWrapper.standard.set(testDouble, forKey: testKey) 95 | 96 | XCTAssertTrue(valueSaved, "Double value did not save to Keychain") 97 | 98 | // clean up keychain 99 | KeychainWrapper.standard.removeObject(forKey: testKey) 100 | } 101 | 102 | func testDoubleRetrieval() { 103 | KeychainWrapper.standard.set(testDouble, forKey: testKey) 104 | 105 | if let retrievedValue = KeychainWrapper.standard.double(forKey: testKey) { 106 | XCTAssertEqual(retrievedValue, testDouble, "Double value retrieved for key should equal value saved for key") 107 | } else { 108 | XCTFail("Double value for Key not found") 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/xcshareddata/xcschemes/TestHostAppTvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 68 | 74 | 75 | 76 | 77 | 78 | 79 | 85 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/xcshareddata/xcschemes/TestHostAppOSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 68 | 74 | 75 | 76 | 77 | 78 | 79 | 85 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/xcshareddata/xcschemes/SwiftKeychainWrapper.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /SwiftKeychainWrapper.xcodeproj/xcshareddata/xcschemes/TestHostApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 101 | 107 | 108 | 109 | 110 | 112 | 113 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftKeychainWrapper 2 | 3 | A simple wrapper for the iOS Keychain to allow you to use it in a similar fashion to User Defaults. Written in Swift. 4 | 5 | Provides singleton instance that is setup to work for most needs. Use `KeychainWrapper.standard` to access the singleton instance. 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | Users that want to deviate from this default implementation, now can do so in 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. 12 | 13 | ## General Usage 14 | 15 | Add a string value to keychain: 16 | ``` 17 | let saveSuccessful: Bool = KeychainWrapper.standard.set("Some String", forKey: "myKey") 18 | ``` 19 | 20 | Retrieve a string value from keychain: 21 | ``` 22 | let retrievedString: String? = KeychainWrapper.standard.string(forKey: "myKey") 23 | ``` 24 | 25 | Remove a string value from keychain: 26 | ``` 27 | let removeSuccessful: Bool = KeychainWrapper.standard.removeObject(forKey: "myKey") 28 | ``` 29 | 30 | ## Custom Instance 31 | 32 | 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 keycahin under different identifiers. 33 | 34 | To share keychain items between your applications, you may specify an access group and use that same access group in each application. 35 | 36 | To set a custom service name identifier or access group, you may now create your own instance of the keychain wrapper as follows: 37 | 38 | ``` 39 | let uniqueServiceName = "customServiceName" 40 | let uniqueAccessGroup = "sharedAccessGroupName" 41 | let customKeychainWrapperInstance = KeychainWrapper(serviceName: uniqueServiceName, accessGroup: uniqueAccessGroup) 42 | ``` 43 | The custom instance can then be used in place of the shared instance or static accessors: 44 | 45 | ``` 46 | let saveSuccessful: Bool = customKeychainWrapperInstance.set("Some String", forKey: "myKey") 47 | 48 | let retrievedString: String? = customKeychainWrapperInstance.string(forKey: "myKey") 49 | 50 | let removeSuccessful: Bool = customKeychainWrapperInstance.removeObject(forKey: "myKey") 51 | ``` 52 | 53 | ## Accessibility Options 54 | 55 | 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: 56 | 57 | ``` 58 | KeychainWrapper.standard.set("Some String", forKey: "myKey", withAccessibility: .AfterFirstUnlock) 59 | ``` 60 | 61 | ## Installation 62 | 63 | #### CocoaPods 64 | You can use [CocoaPods](http://cocoapods.org/) to install SwiftKeychainWrapper by adding it to your `Podfile`: 65 | 66 | ``` ruby 67 | use_frameworks! 68 | platform :ios, '8.0' 69 | 70 | target 'target_name' do 71 | pod 'SwiftKeychainWrapper' 72 | end 73 | ``` 74 | 75 | To use the keychain wrapper in your app, import SwiftKeychainWrapper into the file(s) where you want to use it. 76 | 77 | ``` 78 | import SwiftKeychainWrapper 79 | ``` 80 | 81 | #### Carthage 82 | You can use [Carthage](https://github.com/Carthage/Carthage) to install SwiftKeychainWrapper by adding it to your `Cartfile`. 83 | 84 | Swift 3.0: 85 | ``` 86 | github "jrendel/SwiftKeychainWrapper" ~> 3.0 87 | ``` 88 | 89 | Swift 2.3: 90 | ``` 91 | github "jrendel/SwiftKeychainWrapper" == 2.1.1 92 | ``` 93 | 94 | #### Manually 95 | Download and drop ```KeychainWrapper.swift``` and ```KeychainItemAcessibility.swift``` into your project. 96 | 97 | 98 | ## Release History 99 | 100 | * 3.1 101 | * Updates for Swift 3.1 102 | 103 | * 3.0.1 104 | * Added a host app for the unit tests to get around the issue with keychain access not working the same on iOS 10 simulators 105 | * Minor update to readme instructions 106 | 107 | * 3.0 108 | * Swift 3.0 update. Contains breaking API changes. 2.2.0 and 2.2.1 are now rolled into 3.0 109 | 110 | * 2.2.1 (Removed from Cocoapods) 111 | * Syntax updates to be more Swift 3 like 112 | 113 | * 2.2 (Removed from Cocoapods) 114 | * Updated to support Swift 3.0 115 | * Remove deprecated functions (static access) 116 | 117 | * 2.1 118 | * Updated to support Swift 2.3 119 | 120 | * 2.0 121 | * 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 122 | * 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. 123 | * Addtional options have been provided to alter the keychain accessibility for each key value saved. 124 | 125 | * 1.0.11 126 | * Update for Swift 2.0 127 | 128 | * 1.0.10 129 | * Update License info. Merged Pull Request with Carthage support. 130 | 131 | * 1.0.8 132 | * Update for Swift 1.2 133 | 134 | * 1.0.7 135 | * 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. 136 | 137 | * 1.0.6 138 | * Support for Access Groups 139 | * SwiftKeychainWrapperExample has been updated to show usage with an Access Group: https://github.com/jrendel/SwiftKeychainWrapperExample 140 | 141 | * 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. 142 | 143 | * 1.0.5 144 | * 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. 145 | 146 | * To see an example of usage with CocoaPods, I've created the repo SwiftKeychainWrapperExample: https://github.com/jrendel/SwiftKeychainWrapperExample 147 | 148 | * 1.0.2 149 | * Updated for Xcode 6.1 150 | 151 | --- 152 | 153 | 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: 154 | 155 | http://www.raywenderlich.com/6475/basic-security-in-ios-5-tutorial-part-1 156 | 157 | This is a rewrite of that code in Swift. 158 | 159 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 160 | -------------------------------------------------------------------------------- /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 | 11 | #if os(OSX) 12 | import SwiftKeychainWrapperOSX 13 | #elseif os(iOS) 14 | import SwiftKeychainWrapper 15 | #elseif os(tvOS) 16 | import SwiftKeychainWrapperTvOS 17 | #endif 18 | 19 | class KeychainWrapperDefaultWrapperTests: XCTestCase { 20 | let testKey = "acessorTestKey" 21 | let testString = "This is a test" 22 | 23 | let testKey2 = "acessorTestKey2" 24 | let testString2 = "Test 2 String" 25 | 26 | override func setUp() { 27 | super.setUp() 28 | // Put setup code here. This method is called before the invocation of each test method in the class. 29 | } 30 | 31 | override func tearDown() { 32 | // Put teardown code here. This method is called after the invocation of each test method in the class. 33 | 34 | // clean up keychain 35 | KeychainWrapper.standard.removeObject(forKey: testKey) 36 | KeychainWrapper.standard.removeObject(forKey: testKey2) 37 | 38 | super.tearDown() 39 | } 40 | 41 | func testDefaultServiceName() { 42 | let bundleIdentifier = Bundle.main.bundleIdentifier 43 | if let bundleIdentifierString = bundleIdentifier { 44 | XCTAssertEqual(KeychainWrapper.standard.serviceName, bundleIdentifierString, "Service Name should be equal to the bundle identifier when it is accessible") 45 | } else { 46 | XCTAssertEqual(KeychainWrapper.standard.serviceName, "SwiftKeychainWrapper", "Service Name should be equal to SwiftKeychainWrapper when the bundle identifier is not accessible") 47 | } 48 | } 49 | 50 | func testDefaultAccessGroup() { 51 | XCTAssertNil(KeychainWrapper.standard.accessGroup, "Access Group should be nil when nothing is set") 52 | } 53 | 54 | func testHasValueForKey() { 55 | XCTAssertFalse(KeychainWrapper.standard.hasValue(forKey: testKey), "Keychain should not have a value for the test key") 56 | 57 | KeychainWrapper.standard.set(testString, forKey: testKey) 58 | 59 | XCTAssertTrue(KeychainWrapper.standard.hasValue(forKey: testKey), "Keychain should have a value for the test key after it is set") 60 | } 61 | 62 | func testRemoveObjectFromKeychain() { 63 | KeychainWrapper.standard.set(testString, forKey: testKey) 64 | 65 | XCTAssertTrue(KeychainWrapper.standard.hasValue(forKey: testKey), "Keychain should have a value for the test key after it is set") 66 | 67 | KeychainWrapper.standard.removeObject(forKey: testKey) 68 | 69 | XCTAssertFalse(KeychainWrapper.standard.hasValue(forKey: testKey), "Keychain should not have a value for the test key after it is removed") 70 | } 71 | 72 | func testStringSave() { 73 | let stringSaved = KeychainWrapper.standard.set(testString, forKey: testKey) 74 | 75 | XCTAssertTrue(stringSaved, "String did not save to Keychain") 76 | 77 | // clean up keychain 78 | KeychainWrapper.standard.removeObject(forKey: testKey) 79 | } 80 | 81 | func testStringRetrieval() { 82 | KeychainWrapper.standard.set(testString, forKey: testKey) 83 | 84 | if let retrievedString = KeychainWrapper.standard.string(forKey: testKey) { 85 | XCTAssertEqual(retrievedString, testString, "String retrieved for key should equal string saved for key") 86 | } else { 87 | XCTFail("String for Key not found") 88 | } 89 | } 90 | 91 | func testStringRetrievalWhenValueDoesNotExist() { 92 | let retrievedString = KeychainWrapper.standard.string(forKey: testKey) 93 | XCTAssertNil(retrievedString, "String for Key should not exist") 94 | } 95 | 96 | func testMultipleStringSave() { 97 | if !KeychainWrapper.standard.set(testString, forKey: testKey) { 98 | XCTFail("String for testKey did not save") 99 | } 100 | 101 | if !KeychainWrapper.standard.set(testString2, forKey: testKey2) { 102 | XCTFail("String for testKey2 did not save") 103 | } 104 | 105 | if let string1Retrieved = KeychainWrapper.standard.string(forKey: testKey) { 106 | XCTAssertEqual(string1Retrieved, testString, "String retrieved for testKey should match string saved to testKey") 107 | } else { 108 | XCTFail("String for testKey could not be retrieved") 109 | } 110 | 111 | if let string2Retrieved = KeychainWrapper.standard.string(forKey: testKey2) { 112 | XCTAssertEqual(string2Retrieved, testString2, "String retrieved for testKey2 should match string saved to testKey2") 113 | } else { 114 | XCTFail("String for testKey2 could not be retrieved") 115 | } 116 | } 117 | 118 | func testMultipleStringsSavedToSameKey() { 119 | 120 | if !KeychainWrapper.standard.set(testString, forKey: testKey) { 121 | XCTFail("String for testKey did not save") 122 | } 123 | 124 | if let string1Retrieved = KeychainWrapper.standard.string(forKey: testKey) { 125 | XCTAssertEqual(string1Retrieved, testString, "String retrieved for testKey after first save should match first string saved testKey") 126 | } else { 127 | XCTFail("String for testKey could not be retrieved") 128 | } 129 | 130 | if !KeychainWrapper.standard.set(testString2, forKey: testKey) { 131 | XCTFail("String for testKey did not update") 132 | } 133 | 134 | if let string2Retrieved = KeychainWrapper.standard.string(forKey: testKey) { 135 | XCTAssertEqual(string2Retrieved, testString2, "String retrieved for testKey after update should match second string saved to testKey") 136 | } else { 137 | XCTFail("String for testKey could not be retrieved after update") 138 | } 139 | } 140 | 141 | func testNSCodingObjectSave() { 142 | let myTestObject = TestObject() 143 | let objectSaved = KeychainWrapper.standard.set(myTestObject, forKey: testKey) 144 | 145 | XCTAssertTrue(objectSaved, "Object that implements NSCoding should save to Keychain") 146 | } 147 | 148 | func testNSCodingObjectRetrieval() { 149 | let testInt: Int = 9 150 | let myTestObject = TestObject() 151 | myTestObject.objectName = testString 152 | myTestObject.objectRating = testInt 153 | 154 | KeychainWrapper.standard.set(myTestObject, forKey: testKey) 155 | 156 | if let retrievedObject = KeychainWrapper.standard.object(forKey: testKey) as? TestObject{ 157 | XCTAssertEqual(retrievedObject.objectName, testString, "NSCoding compliant object retrieved for key should have objectName property equal to what it was stored with") 158 | XCTAssertEqual(retrievedObject.objectRating, testInt, "NSCoding compliant object retrieved for key should have objectRating property equal to what it was stored with") 159 | } else { 160 | XCTFail("Object for Key not found") 161 | } 162 | } 163 | 164 | func testNSCodingObjectRetrievalWhenValueDoesNotExist() { 165 | let retrievedObject = KeychainWrapper.standard.object(forKey: testKey) as? TestObject 166 | XCTAssertNil(retrievedObject, "Object for Key should not exist") 167 | } 168 | 169 | func testDataSave() { 170 | let testData = testString.data(using: String.Encoding.utf8) 171 | 172 | if let data = testData { 173 | let dataSaved = KeychainWrapper.standard.set(data, forKey: testKey) 174 | 175 | XCTAssertTrue(dataSaved, "Data did not save to Keychain") 176 | } else { 177 | XCTFail("Failed to create Data") 178 | } 179 | } 180 | 181 | func testDataRetrieval() { 182 | guard let testData = testString.data(using: String.Encoding.utf8) else { 183 | XCTFail("Failed to create Data") 184 | return 185 | } 186 | 187 | KeychainWrapper.standard.set(testData, forKey: testKey) 188 | 189 | guard let retrievedData = KeychainWrapper.standard.data(forKey: testKey) else { 190 | XCTFail("Data for Key not found") 191 | return 192 | } 193 | 194 | if KeychainWrapper.standard.dataRef(forKey: testKey) == nil { 195 | XCTFail("Data references for Key not found") 196 | } 197 | 198 | if let retrievedString = String(data: retrievedData, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) { 199 | XCTAssertEqual(retrievedString, testString, "String retrieved from data for key should equal string saved as data for key") 200 | } else { 201 | XCTFail("Output Data for key does not match input. ") 202 | } 203 | } 204 | 205 | func testDataRetrievalWhenValueDoesNotExist() { 206 | let retrievedData = KeychainWrapper.standard.data(forKey: testKey) 207 | XCTAssertNil(retrievedData, "Data for Key should not exist") 208 | 209 | let retrievedDataRef = KeychainWrapper.standard.dataRef(forKey: testKey) 210 | XCTAssertNil(retrievedDataRef, "Data ref for Key should not exist") 211 | } 212 | 213 | func testKeysEmpty() { 214 | let keys = KeychainWrapper.standard.allKeys() 215 | XCTAssertEqual(keys, [], "Empty keychain should not contain keys") 216 | } 217 | 218 | func testKeysOneKey() { 219 | let result = KeychainWrapper.standard.set("testString", forKey: testKey) 220 | 221 | let keys = KeychainWrapper.standard.allKeys() 222 | print(keys) 223 | XCTAssertEqual(keys, [testKey], "Keychain should contain the inserted key") 224 | } 225 | 226 | func testKeysMultipleKeys() { 227 | KeychainWrapper.standard.set(testString, forKey: testKey) 228 | KeychainWrapper.standard.set(testString2, forKey: testKey2) 229 | 230 | let keys = KeychainWrapper.standard.allKeys() 231 | XCTAssertEqual(keys, [testKey, testKey2], "Keychain should contain the inserted keys") 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /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 | 43 | /// 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. 44 | open class KeychainWrapper { 45 | 46 | @available(*, deprecated: 2.2.1, message: "KeychainWrapper.defaultKeychainWrapper is deprecated, use KeychainWrapper.standard instead") 47 | public static let defaultKeychainWrapper = KeychainWrapper.standard 48 | 49 | /// Default keychain wrapper access 50 | public static let standard = KeychainWrapper() 51 | 52 | /// 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. 53 | private (set) public var serviceName: String 54 | 55 | /// 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. 56 | private (set) public var accessGroup: String? 57 | 58 | private static let defaultServiceName: String = { 59 | return Bundle.main.bundleIdentifier ?? "SwiftKeychainWrapper" 60 | }() 61 | 62 | private convenience init() { 63 | self.init(serviceName: KeychainWrapper.defaultServiceName) 64 | } 65 | 66 | /// Create a custom instance of KeychainWrapper with a custom Service Name and optional custom access group. 67 | /// 68 | /// - parameter serviceName: The ServiceName for this instance. Used to uniquely identify all keys stored using this keychain wrapper instance. 69 | /// - parameter accessGroup: Optional unique AccessGroup for this instance. Use a matching AccessGroup between applications to allow shared keychain access. 70 | public init(serviceName: String, accessGroup: String? = nil) { 71 | self.serviceName = serviceName 72 | self.accessGroup = accessGroup 73 | } 74 | 75 | // MARK:- Public Methods 76 | 77 | /// Checks if keychain data exists for a specified key. 78 | /// 79 | /// - parameter forKey: The key to check for. 80 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 81 | /// - returns: True if a value exists for the key. False otherwise. 82 | open func hasValue(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 83 | if let _ = data(forKey: key, withAccessibility: accessibility) { 84 | return true 85 | } else { 86 | return false 87 | } 88 | } 89 | 90 | open func accessibilityOfKey(_ key: String) -> KeychainItemAccessibility? { 91 | var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key) 92 | 93 | // Remove accessibility attribute 94 | keychainQueryDictionary.removeValue(forKey: SecAttrAccessible) 95 | 96 | // Limit search results to one 97 | keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne 98 | 99 | // Specify we want SecAttrAccessible returned 100 | keychainQueryDictionary[SecReturnAttributes] = kCFBooleanTrue 101 | 102 | // Search 103 | var result: AnyObject? 104 | let status = SecItemCopyMatching(keychainQueryDictionary as CFDictionary, &result) 105 | 106 | guard status == noErr, let resultsDictionary = result as? [String:AnyObject], let accessibilityAttrValue = resultsDictionary[SecAttrAccessible] as? String else { 107 | return nil 108 | } 109 | 110 | return KeychainItemAccessibility.accessibilityForAttributeValue(accessibilityAttrValue as CFString) 111 | } 112 | 113 | /// Get the keys of all keychain entries matching the current ServiceName and AccessGroup if one is set. 114 | open func allKeys() -> Set { 115 | var keychainQueryDictionary: [String:Any] = [ 116 | SecClass: kSecClassGenericPassword, 117 | SecAttrService: serviceName, 118 | SecReturnAttributes: kCFBooleanTrue, 119 | SecMatchLimit: kSecMatchLimitAll, 120 | ] 121 | 122 | if let accessGroup = self.accessGroup { 123 | keychainQueryDictionary[SecAttrAccessGroup] = accessGroup 124 | } 125 | 126 | var result: AnyObject? 127 | let status = SecItemCopyMatching(keychainQueryDictionary as CFDictionary, &result) 128 | 129 | guard status == errSecSuccess else { return [] } 130 | 131 | var keys = Set() 132 | if let results = result as? [[AnyHashable: Any]] { 133 | for attributes in results { 134 | if let accountName = attributes[SecAttrAccount] as? String { 135 | keys.insert(accountName) 136 | } 137 | } 138 | } 139 | return keys 140 | } 141 | 142 | // MARK: Public Getters 143 | 144 | open func integer(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Int? { 145 | guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else { 146 | return nil 147 | } 148 | 149 | return numberValue.intValue 150 | } 151 | 152 | open func float(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Float? { 153 | guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else { 154 | return nil 155 | } 156 | 157 | return numberValue.floatValue 158 | } 159 | 160 | open func double(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Double? { 161 | guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else { 162 | return nil 163 | } 164 | 165 | return numberValue.doubleValue 166 | } 167 | 168 | open func bool(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool? { 169 | guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else { 170 | return nil 171 | } 172 | 173 | return numberValue.boolValue 174 | } 175 | 176 | /// Returns a string value for a specified key. 177 | /// 178 | /// - parameter forKey: The key to lookup data for. 179 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 180 | /// - 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. 181 | open func string(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> String? { 182 | guard let keychainData = data(forKey: key, withAccessibility: accessibility) else { 183 | return nil 184 | } 185 | 186 | return String(data: keychainData, encoding: String.Encoding.utf8) as String? 187 | } 188 | 189 | /// Returns an object that conforms to NSCoding for a specified key. 190 | /// 191 | /// - parameter forKey: The key to lookup data for. 192 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 193 | /// - returns: The decoded object associated with the key if it exists. If no data exists, or the data found cannot be decoded, returns nil. 194 | open func object(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> NSCoding? { 195 | guard let keychainData = data(forKey: key, withAccessibility: accessibility) else { 196 | return nil 197 | } 198 | 199 | return NSKeyedUnarchiver.unarchiveObject(with: keychainData) as? NSCoding 200 | } 201 | 202 | 203 | /// Returns a Data object for a specified key. 204 | /// 205 | /// - parameter forKey: The key to lookup data for. 206 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 207 | /// - returns: The Data object associated with the key if it exists. If no data exists, returns nil. 208 | open func data(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Data? { 209 | var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility) 210 | 211 | // Limit search results to one 212 | keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne 213 | 214 | // Specify we want Data/CFData returned 215 | keychainQueryDictionary[SecReturnData] = kCFBooleanTrue 216 | 217 | // Search 218 | var result: AnyObject? 219 | let status = SecItemCopyMatching(keychainQueryDictionary as CFDictionary, &result) 220 | 221 | return status == noErr ? result as? Data : nil 222 | } 223 | 224 | 225 | /// Returns a persistent data reference object for a specified key. 226 | /// 227 | /// - parameter forKey: The key to lookup data for. 228 | /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item. 229 | /// - returns: The persistent data reference object associated with the key if it exists. If no data exists, returns nil. 230 | open func dataRef(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Data? { 231 | var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility) 232 | 233 | // Limit search results to one 234 | keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne 235 | 236 | // Specify we want persistent Data/CFData reference returned 237 | keychainQueryDictionary[SecReturnPersistentRef] = kCFBooleanTrue 238 | 239 | // Search 240 | var result: AnyObject? 241 | let status = SecItemCopyMatching(keychainQueryDictionary as CFDictionary, &result) 242 | 243 | return status == noErr ? result as? Data : nil 244 | } 245 | 246 | // MARK: Public Setters 247 | 248 | @discardableResult open func set(_ value: Int, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 249 | return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility) 250 | } 251 | 252 | @discardableResult open func set(_ value: Float, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 253 | return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility) 254 | } 255 | 256 | @discardableResult open func set(_ value: Double, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 257 | return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility) 258 | } 259 | 260 | @discardableResult open func set(_ value: Bool, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 261 | return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility) 262 | } 263 | 264 | /// 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. 265 | /// 266 | /// - parameter value: The String value to save. 267 | /// - parameter forKey: The key to save the String under. 268 | /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. 269 | /// - returns: True if the save was successful, false otherwise. 270 | @discardableResult open func set(_ value: String, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 271 | if let data = value.data(using: .utf8) { 272 | return set(data, forKey: key, withAccessibility: accessibility) 273 | } else { 274 | return false 275 | } 276 | } 277 | 278 | /// 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. 279 | /// 280 | /// - parameter value: The NSCoding compliant object to save. 281 | /// - parameter forKey: The key to save the object under. 282 | /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. 283 | /// - returns: True if the save was successful, false otherwise. 284 | @discardableResult open func set(_ value: NSCoding, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 285 | let data = NSKeyedArchiver.archivedData(withRootObject: value) 286 | 287 | return set(data, forKey: key, withAccessibility: accessibility) 288 | } 289 | 290 | /// 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. 291 | /// 292 | /// - parameter value: The Data object to save. 293 | /// - parameter forKey: The key to save the object under. 294 | /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. 295 | /// - returns: True if the save was successful, false otherwise. 296 | @discardableResult open func set(_ value: Data, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 297 | var keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility) 298 | 299 | keychainQueryDictionary[SecValueData] = value 300 | 301 | if let accessibility = accessibility { 302 | keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue 303 | } else { 304 | // Assign default protection - Protect the keychain entry so it's only valid when the device is unlocked 305 | keychainQueryDictionary[SecAttrAccessible] = KeychainItemAccessibility.whenUnlocked.keychainAttrValue 306 | } 307 | 308 | let status: OSStatus = SecItemAdd(keychainQueryDictionary as CFDictionary, nil) 309 | 310 | if status == errSecSuccess { 311 | return true 312 | } else if status == errSecDuplicateItem { 313 | return update(value, forKey: key, withAccessibility: accessibility) 314 | } else { 315 | return false 316 | } 317 | } 318 | 319 | @available(*, deprecated: 2.2.1, message: "remove is deprecated, use removeObject instead") 320 | @discardableResult open func remove(key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 321 | return removeObject(forKey: key, withAccessibility: accessibility) 322 | } 323 | 324 | /// 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. 325 | /// 326 | /// - parameter forKey: The key value to remove data for. 327 | /// - parameter withAccessibility: Optional accessibility level to use when looking up the keychain item. 328 | /// - returns: True if successful, false otherwise. 329 | @discardableResult open func removeObject(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 330 | let keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility) 331 | 332 | // Delete 333 | let status: OSStatus = SecItemDelete(keychainQueryDictionary as CFDictionary) 334 | 335 | if status == errSecSuccess { 336 | return true 337 | } else { 338 | return false 339 | } 340 | } 341 | 342 | /// Remove all keychain data added through KeychainWrapper. This will only delete items matching the currnt ServiceName and AccessGroup if one is set. 343 | open func removeAllKeys() -> Bool { 344 | // Setup dictionary to access keychain and specify we are using a generic password (rather than a certificate, internet password, etc) 345 | var keychainQueryDictionary: [String:Any] = [SecClass:kSecClassGenericPassword] 346 | 347 | // Uniquely identify this keychain accessor 348 | keychainQueryDictionary[SecAttrService] = serviceName 349 | 350 | // Set the keychain access group if defined 351 | if let accessGroup = self.accessGroup { 352 | keychainQueryDictionary[SecAttrAccessGroup] = accessGroup 353 | } 354 | 355 | let status: OSStatus = SecItemDelete(keychainQueryDictionary as CFDictionary) 356 | 357 | if status == errSecSuccess { 358 | return true 359 | } else { 360 | return false 361 | } 362 | } 363 | 364 | /// Remove all keychain data, including data not added through keychain wrapper. 365 | /// 366 | /// - Warning: This may remove custom keychain entries you did not add via SwiftKeychainWrapper. 367 | /// 368 | open class func wipeKeychain() { 369 | deleteKeychainSecClass(kSecClassGenericPassword) // Generic password items 370 | deleteKeychainSecClass(kSecClassInternetPassword) // Internet password items 371 | deleteKeychainSecClass(kSecClassCertificate) // Certificate items 372 | deleteKeychainSecClass(kSecClassKey) // Cryptographic key items 373 | deleteKeychainSecClass(kSecClassIdentity) // Identity items 374 | } 375 | 376 | // MARK:- Private Methods 377 | 378 | /// Remove all items for a given Keychain Item Class 379 | /// 380 | /// 381 | @discardableResult private class func deleteKeychainSecClass(_ secClass: AnyObject) -> Bool { 382 | let query = [SecClass: secClass] 383 | let status: OSStatus = SecItemDelete(query as CFDictionary) 384 | 385 | if status == errSecSuccess { 386 | return true 387 | } else { 388 | return false 389 | } 390 | } 391 | 392 | /// Update existing data associated with a specified key name. The existing data will be overwritten by the new data. 393 | private func update(_ value: Data, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool { 394 | var keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility) 395 | let updateDictionary = [SecValueData:value] 396 | 397 | // on update, only set accessibility if passed in 398 | if let accessibility = accessibility { 399 | keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue 400 | } 401 | 402 | // Update 403 | let status: OSStatus = SecItemUpdate(keychainQueryDictionary as CFDictionary, updateDictionary as CFDictionary) 404 | 405 | if status == errSecSuccess { 406 | return true 407 | } else { 408 | return false 409 | } 410 | } 411 | 412 | /// 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. 413 | /// 414 | /// - parameter forKey: The key this query is for 415 | /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. If none is provided, will default to .WhenUnlocked 416 | /// - returns: A dictionary with all the needed properties setup to access the keychain on iOS 417 | private func setupKeychainQueryDictionary(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> [String:Any] { 418 | // Setup default access as generic password (rather than a certificate, internet password, etc) 419 | var keychainQueryDictionary: [String:Any] = [SecClass:kSecClassGenericPassword] 420 | 421 | // Uniquely identify this keychain accessor 422 | keychainQueryDictionary[SecAttrService] = serviceName 423 | 424 | // Only set accessibiilty if its passed in, we don't want to default it here in case the user didn't want it set 425 | if let accessibility = accessibility { 426 | keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue 427 | } 428 | 429 | // Set the keychain access group if defined 430 | if let accessGroup = self.accessGroup { 431 | keychainQueryDictionary[SecAttrAccessGroup] = accessGroup 432 | } 433 | 434 | // Uniquely identify the account who will be accessing the keychain 435 | let encodedIdentifier: Data? = key.data(using: String.Encoding.utf8) 436 | 437 | keychainQueryDictionary[SecAttrGeneric] = encodedIdentifier 438 | 439 | keychainQueryDictionary[SecAttrAccount] = key 440 | 441 | return keychainQueryDictionary 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /TestHostAppOSX/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 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | Default 530 | 531 | 532 | 533 | 534 | 535 | 536 | Left to Right 537 | 538 | 539 | 540 | 541 | 542 | 543 | Right to Left 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | Default 555 | 556 | 557 | 558 | 559 | 560 | 561 | Left to Right 562 | 563 | 564 | 565 | 566 | 567 | 568 | Right to Left 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | --------------------------------------------------------------------------------