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.

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