You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

602 lines
18 KiB

  1. # KeychainAccess
  2. [![CI Status](http://img.shields.io/travis/kishikawakatsumi/KeychainAccess.svg)](https://travis-ci.org/kishikawakatsumi/KeychainAccess)
  3. [![codecov](https://codecov.io/gh/kishikawakatsumi/KeychainAccess/branch/master/graph/badge.svg)](https://codecov.io/gh/kishikawakatsumi/KeychainAccess)
  4. [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
  5. [![Version](https://img.shields.io/cocoapods/v/KeychainAccess.svg)](http://cocoadocs.org/docsets/KeychainAccess)
  6. [![Platform](https://img.shields.io/cocoapods/p/KeychainAccess.svg)](http://cocoadocs.org/docsets/KeychainAccess)
  7. [![Swift 3.x](https://img.shields.io/badge/Swift-3.x-orange.svg?style=flat)](https://swift.org/)
  8. [![Swift 4.0](https://img.shields.io/badge/Swift-4.0-orange.svg?style=flat)](https://swift.org/)
  9. [![Swift 4.1](https://img.shields.io/badge/Swift-4.1-orange.svg?style=flat)](https://swift.org/)
  10. [![Swift 4.2](https://img.shields.io/badge/Swift-4.2-orange.svg?style=flat)](https://swift.org/)
  11. KeychainAccess is a simple Swift wrapper for Keychain that works on iOS and OS X. Makes using Keychain APIs extremely easy and much more palatable to use in Swift.
  12. <img src="https://raw.githubusercontent.com/kishikawakatsumi/KeychainAccess/master/Screenshots/01.png" width="320px" />
  13. <img src="https://raw.githubusercontent.com/kishikawakatsumi/KeychainAccess/master/Screenshots/02.png" width="320px" />
  14. <img src="https://raw.githubusercontent.com/kishikawakatsumi/KeychainAccess/master/Screenshots/03.png" width="320px" />
  15. ## :bulb: Features
  16. - Simple interface
  17. - Support access group
  18. - [Support accessibility](#accessibility)
  19. - [Support iCloud sharing](#icloud_sharing)
  20. - **[Support TouchID and Keychain integration (iOS 8+)](#touch_id_integration)**
  21. - **[Support Shared Web Credentials (iOS 8+)](#shared_web_credentials)**
  22. - [Works on both iOS & OS X](#requirements)
  23. - [watchOS and tvOS are supported](#requirements)
  24. - **[Swift 4 & Swift 3 compatible](#requirements)**
  25. ## :book: Usage
  26. ##### :eyes: See also:
  27. - [:link: iOS Example Project](https://github.com/kishikawakatsumi/KeychainAccess/tree/master/Examples/Example-iOS)
  28. ### :key: Basics
  29. #### Saving Application Password
  30. ```swift
  31. let keychain = Keychain(service: "com.example.github-token")
  32. keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
  33. ```
  34. #### Saving Internet Password
  35. ```swift
  36. let keychain = Keychain(server: "https://github.com", protocolType: .https)
  37. keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
  38. ```
  39. ### :key: Instantiation
  40. #### Create Keychain for Application Password
  41. ```swift
  42. let keychain = Keychain(service: "com.example.github-token")
  43. ```
  44. ```swift
  45. let keychain = Keychain(service: "com.example.github-token", accessGroup: "12ABCD3E4F.shared")
  46. ```
  47. #### Create Keychain for Internet Password
  48. ```swift
  49. let keychain = Keychain(server: "https://github.com", protocolType: .https)
  50. ```
  51. ```swift
  52. let keychain = Keychain(server: "https://github.com", protocolType: .https, authenticationType: .htmlForm)
  53. ```
  54. ### :key: Adding an item
  55. #### subscripting
  56. ##### for String
  57. ```swift
  58. keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
  59. ```
  60. ```swift
  61. keychain[string: "kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
  62. ```
  63. ##### for NSData
  64. ```swift
  65. keychain[data: "secret"] = NSData(contentsOfFile: "secret.bin")
  66. ```
  67. #### set method
  68. ```swift
  69. keychain.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
  70. ```
  71. #### error handling
  72. ```swift
  73. do {
  74. try keychain.set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
  75. }
  76. catch let error {
  77. print(error)
  78. }
  79. ```
  80. ### :key: Obtaining an item
  81. #### subscripting
  82. ##### for String (If the value is NSData, attempt to convert to String)
  83. ```swift
  84. let token = keychain["kishikawakatsumi"]
  85. ```
  86. ```swift
  87. let token = keychain[string: "kishikawakatsumi"]
  88. ```
  89. ##### for NSData
  90. ```swift
  91. let secretData = keychain[data: "secret"]
  92. ```
  93. #### get methods
  94. ##### as String
  95. ```swift
  96. let token = try? keychain.get("kishikawakatsumi")
  97. ```
  98. ```swift
  99. let token = try? keychain.getString("kishikawakatsumi")
  100. ```
  101. ##### as NSData
  102. ```swift
  103. let data = try? keychain.getData("kishikawakatsumi")
  104. ```
  105. ### :key: Removing an item
  106. #### subscripting
  107. ```swift
  108. keychain["kishikawakatsumi"] = nil
  109. ```
  110. #### remove method
  111. ```swift
  112. do {
  113. try keychain.remove("kishikawakatsumi")
  114. } catch let error {
  115. print("error: \(error)")
  116. }
  117. ```
  118. ### :key: Set Label and Comment
  119. ```swift
  120. let keychain = Keychain(server: "https://github.com", protocolType: .https)
  121. do {
  122. try keychain
  123. .label("github.com (kishikawakatsumi)")
  124. .comment("github access token")
  125. .set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
  126. } catch let error {
  127. print("error: \(error)")
  128. }
  129. ```
  130. ### :key: Obtaining Other Attributes
  131. #### PersistentRef
  132. ```swift
  133. let keychain = Keychain()
  134. let persistentRef = keychain[attributes: "kishikawakatsumi"].persistentRef
  135. ...
  136. ```
  137. #### Creation Date
  138. ```swift
  139. let keychain = Keychain()
  140. let creationDate = keychain[attributes: "kishikawakatsumi"].creationDate
  141. ...
  142. ```
  143. #### All Attributes
  144. ```swift
  145. let keychain = Keychain()
  146. do {
  147. let attributes = try keychain.get("kishikawakatsumi") { $0 }
  148. print(attributes.comment)
  149. print(attributes.label)
  150. print(attributes.creator)
  151. ...
  152. } catch let error {
  153. print("error: \(error)")
  154. }
  155. ```
  156. ##### subscripting
  157. ```swift
  158. let keychain = Keychain()
  159. let attributes = keychain[attributes: "kishikawakatsumi"]
  160. print(attributes.comment)
  161. print(attributes.label)
  162. print(attributes.creator)
  163. ```
  164. ### :key: Configuration (Accessibility, Sharing, iCloud Sync)
  165. **Provides fluent interfaces**
  166. ```swift
  167. let keychain = Keychain(service: "com.example.github-token")
  168. .label("github.com (kishikawakatsumi)")
  169. .synchronizable(true)
  170. .accessibility(.afterFirstUnlock)
  171. ```
  172. #### <a name="accessibility"> Accessibility
  173. ##### Default accessibility matches background application (=kSecAttrAccessibleAfterFirstUnlock)
  174. ```swift
  175. let keychain = Keychain(service: "com.example.github-token")
  176. ```
  177. ##### For background application
  178. ###### Creating instance
  179. ```swift
  180. let keychain = Keychain(service: "com.example.github-token")
  181. .accessibility(.afterFirstUnlock)
  182. keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
  183. ```
  184. ###### One-shot
  185. ```swift
  186. let keychain = Keychain(service: "com.example.github-token")
  187. do {
  188. try keychain
  189. .accessibility(.afterFirstUnlock)
  190. .set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
  191. } catch let error {
  192. print("error: \(error)")
  193. }
  194. ```
  195. ##### For foreground application
  196. ###### Creating instance
  197. ```swift
  198. let keychain = Keychain(service: "com.example.github-token")
  199. .accessibility(.whenUnlocked)
  200. keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
  201. ```
  202. ###### One-shot
  203. ```swift
  204. let keychain = Keychain(service: "com.example.github-token")
  205. do {
  206. try keychain
  207. .accessibility(.whenUnlocked)
  208. .set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
  209. } catch let error {
  210. print("error: \(error)")
  211. }
  212. ```
  213. #### :couple: Sharing Keychain items
  214. ```swift
  215. let keychain = Keychain(service: "com.example.github-token", accessGroup: "12ABCD3E4F.shared")
  216. ```
  217. #### <a name="icloud_sharing"> :arrows_counterclockwise: Synchronizing Keychain items with iCloud
  218. ###### Creating instance
  219. ```swift
  220. let keychain = Keychain(service: "com.example.github-token")
  221. .synchronizable(true)
  222. keychain["kishikawakatsumi"] = "01234567-89ab-cdef-0123-456789abcdef"
  223. ```
  224. ###### One-shot
  225. ```swift
  226. let keychain = Keychain(service: "com.example.github-token")
  227. do {
  228. try keychain
  229. .synchronizable(true)
  230. .set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
  231. } catch let error {
  232. print("error: \(error)")
  233. }
  234. ```
  235. ### <a name="touch_id_integration"> :fu: Touch ID integration
  236. **Any Operation that require authentication must be run in the background thread.**
  237. **If you run in the main thread, UI thread will lock for the system to try to display the authentication dialog.**
  238. #### :closed_lock_with_key: Adding a Touch ID protected item
  239. If you want to store the Touch ID protected Keychain item, specify `accessibility` and `authenticationPolicy` attributes.
  240. ```swift
  241. let keychain = Keychain(service: "com.example.github-token")
  242. DispatchQueue.global().async {
  243. do {
  244. // Should be the secret invalidated when passcode is removed? If not then use `.WhenUnlocked`
  245. try keychain
  246. .accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .userPresence)
  247. .set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
  248. } catch let error {
  249. // Error handling if needed...
  250. }
  251. }
  252. ```
  253. #### :closed_lock_with_key: Updating a Touch ID protected item
  254. The same way as when adding.
  255. **Do not run in the main thread if there is a possibility that the item you are trying to add already exists, and protected.**
  256. **Because updating protected items requires authentication.**
  257. Additionally, you want to show custom authentication prompt message when updating, specify an `authenticationPrompt` attribute.
  258. If the item not protected, the `authenticationPrompt` parameter just be ignored.
  259. ```swift
  260. let keychain = Keychain(service: "com.example.github-token")
  261. DispatchQueue.global().async {
  262. do {
  263. // Should be the secret invalidated when passcode is removed? If not then use `.WhenUnlocked`
  264. try keychain
  265. .accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .userPresence)
  266. .authenticationPrompt("Authenticate to update your access token")
  267. .set("01234567-89ab-cdef-0123-456789abcdef", key: "kishikawakatsumi")
  268. } catch let error {
  269. // Error handling if needed...
  270. }
  271. }
  272. ```
  273. #### :closed_lock_with_key: Obtaining a Touch ID protected item
  274. The same way as when you get a normal item. It will be displayed automatically Touch ID or passcode authentication If the item you try to get is protected.
  275. If you want to show custom authentication prompt message, specify an `authenticationPrompt` attribute.
  276. If the item not protected, the `authenticationPrompt` parameter just be ignored.
  277. ```swift
  278. let keychain = Keychain(service: "com.example.github-token")
  279. DispatchQueue.global().async {
  280. do {
  281. let password = try keychain
  282. .authenticationPrompt("Authenticate to login to server")
  283. .get("kishikawakatsumi")
  284. print("password: \(password)")
  285. } catch let error {
  286. // Error handling if needed...
  287. }
  288. }
  289. ```
  290. #### :closed_lock_with_key: Removing a Touch ID protected item
  291. The same way as when you remove a normal item.
  292. There is no way to show Touch ID or passcode authentication when removing Keychain items.
  293. ```swift
  294. let keychain = Keychain(service: "com.example.github-token")
  295. do {
  296. try keychain.remove("kishikawakatsumi")
  297. } catch let error {
  298. // Error handling if needed...
  299. }
  300. ```
  301. ### <a name="shared_web_credentials"> :key: Shared Web Credentials
  302. > Shared web credentials is a programming interface that enables native iOS apps to share credentials with their website counterparts. For example, a user may log in to a website in Safari, entering a user name and password, and save those credentials using the iCloud Keychain. Later, the user may run a native app from the same developer, and instead of the app requiring the user to reenter a user name and password, shared web credentials gives it access to the credentials that were entered earlier in Safari. The user can also create new accounts, update passwords, or delete her account from within the app. These changes are then saved and used by Safari.
  303. <https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/>
  304. ```swift
  305. let keychain = Keychain(server: "https://www.kishikawakatsumi.com", protocolType: .HTTPS)
  306. let username = "kishikawakatsumi@mac.com"
  307. // First, check the credential in the app's Keychain
  308. if let password = try? keychain.get(username) {
  309. // If found password in the Keychain,
  310. // then log into the server
  311. } else {
  312. // If not found password in the Keychain,
  313. // try to read from Shared Web Credentials
  314. keychain.getSharedPassword(username) { (password, error) -> () in
  315. if password != nil {
  316. // If found password in the Shared Web Credentials,
  317. // then log into the server
  318. // and save the password to the Keychain
  319. keychain[username] = password
  320. } else {
  321. // If not found password either in the Keychain also Shared Web Credentials,
  322. // prompt for username and password
  323. // Log into server
  324. // If the login is successful,
  325. // save the credentials to both the Keychain and the Shared Web Credentials.
  326. keychain[username] = inputPassword
  327. keychain.setSharedPassword(inputPassword, account: username)
  328. }
  329. }
  330. }
  331. ```
  332. #### Request all associated domain's credentials
  333. ```swift
  334. Keychain.requestSharedWebCredential { (credentials, error) -> () in
  335. }
  336. ```
  337. #### Generate strong random password
  338. Generate strong random password that is in the same format used by Safari autofill (xxx-xxx-xxx-xxx).
  339. ```swift
  340. let password = Keychain.generatePassword() // => Nhu-GKm-s3n-pMx
  341. ```
  342. #### How to set up Shared Web Credentials
  343. > 1. Add a com.apple.developer.associated-domains entitlement to your app. This entitlement must include all the domains with which you want to share credentials.
  344. > 2. Add an apple-app-site-association file to your website. This file must include application identifiers for all the apps with which the site wants to share credentials, and it must be properly signed.
  345. > 3. When the app is installed, the system downloads and verifies the site association file for each of its associated domains. If the verification is successful, the app is associated with the domain.
  346. **More details:**
  347. <https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/>
  348. ### :key: Debugging
  349. #### Display all stored items if print keychain object
  350. ```swift
  351. let keychain = Keychain(server: "https://github.com", protocolType: .https)
  352. print("\(keychain)")
  353. ```
  354. ```
  355. =>
  356. [
  357. [authenticationType: default, key: kishikawakatsumi, server: github.com, class: internetPassword, protocol: https]
  358. [authenticationType: default, key: hirohamada, server: github.com, class: internetPassword, protocol: https]
  359. [authenticationType: default, key: honeylemon, server: github.com, class: internetPassword, protocol: https]
  360. ]
  361. ```
  362. #### Obtaining all stored keys
  363. ```swift
  364. let keychain = Keychain(server: "https://github.com", protocolType: .https)
  365. let keys = keychain.allKeys()
  366. for key in keys {
  367. print("key: \(key)")
  368. }
  369. ```
  370. ```
  371. =>
  372. key: kishikawakatsumi
  373. key: hirohamada
  374. key: honeylemon
  375. ```
  376. #### Obtaining all stored items
  377. ```swift
  378. let keychain = Keychain(server: "https://github.com", protocolType: .https)
  379. let items = keychain.allItems()
  380. for item in items {
  381. print("item: \(item)")
  382. }
  383. ```
  384. ```
  385. =>
  386. item: [authenticationType: Default, key: kishikawakatsumi, server: github.com, class: InternetPassword, protocol: https]
  387. item: [authenticationType: Default, key: hirohamada, server: github.com, class: InternetPassword, protocol: https]
  388. item: [authenticationType: Default, key: honeylemon, server: github.com, class: InternetPassword, protocol: https]
  389. ```
  390. ## Requirements
  391. | | OS | Swift |
  392. |------------|----------------------------------------|---------------|
  393. | **v1.1.x** | iOS 7+, OSX 10.9+ | 1.1 |
  394. | **v1.2.x** | iOS 7+, OSX 10.9+ | 1.2 |
  395. | **v2.0.x** | iOS 7+, OSX 10.9+, watchOS 2+ | 2.0 |
  396. | **v2.1.x** | iOS 7+, OSX 10.9+, watchOS 2+ | 2.0 |
  397. | **v2.2.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1 |
  398. | **v2.3.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1, 2.2 |
  399. | **v2.4.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 2.2, 2.3 |
  400. | **v3.0.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 3.x |
  401. | **v3.1.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 4.0, 4.1, 4.2 |
  402. ## Installation
  403. ### CocoaPods
  404. KeychainAccess is available through [CocoaPods](http://cocoapods.org). To install
  405. it, simply add the following lines to your Podfile:
  406. ```ruby
  407. use_frameworks!
  408. pod 'KeychainAccess'
  409. ```
  410. ### Carthage
  411. KeychainAccess is available through [Carthage](https://github.com/Carthage/Carthage). To install
  412. it, simply add the following line to your Cartfile:
  413. `github "kishikawakatsumi/KeychainAccess"`
  414. ### Swift Package Manager
  415. KeychainAccess is also available through [Swift Package Manager](https://github.com/apple/swift-package-manager/).
  416. First, create `Package.swift` that its package declaration includes:
  417. ```swift
  418. import PackageDescription
  419. let package = Package(
  420. dependencies: [
  421. .Package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", majorVersion: 2)
  422. ]
  423. )
  424. ```
  425. Then, type
  426. ```shell
  427. $ swift build
  428. ```
  429. ### To manually add to your project
  430. 1. Add `Lib/KeychainAccess.xcodeproj` to your project
  431. 2. Link `KeychainAccess.framework` with your target
  432. 3. Add `Copy Files Build Phase` to include the framework to your application bundle
  433. _See [iOS Example Project](https://github.com/kishikawakatsumi/KeychainAccess/tree/master/Examples/Example-iOS) as reference._
  434. <img src="https://raw.githubusercontent.com/kishikawakatsumi/KeychainAccess/master/Screenshots/Installation.png" width="800px" />
  435. ## Author
  436. kishikawa katsumi, kishikawakatsumi@mac.com
  437. ## License
  438. KeychainAccess is available under the MIT license. See the LICENSE file for more info.