فهرست منبع

Merge pull request #35 from kishikawakatsumi/shared_credential

Support Shared Web Credentials
kishikawa katsumi 10 سال پیش
والد
کامیت
fad166f795
2فایلهای تغییر یافته به همراه168 افزوده شده و 5 حذف شده
  1. 117 5
      Lib/KeychainAccess/Keychain.swift
  2. 51 0
      README.md

+ 117 - 5
Lib/KeychainAccess/Keychain.swift

@@ -347,6 +347,112 @@ public class Keychain {
         return FailableOf(securityError(status: status))
     }
     
+    #if os(iOS)
+    @availability(iOS, introduced=8.0)
+    public func getSharedPassword(completion: (account: String?, password: String?, error: NSError?) -> () = { account, password, error -> () in }) {
+        if let domain = server.host {
+            requestSharedWebCredential(domain: domain, account: nil) { (credentials, error) -> () in
+                if let credential = credentials.first {
+                    let account = credential["account"]
+                    let password = credential["password"]
+                    completion(account: account, password: password, error: error)
+                } else {
+                    completion(account: nil, password: nil, error: error)
+                }
+            }
+        } else {
+            let error = securityError(status: Status.Param.rawValue)
+            completion(account: nil, password: nil, error: error)
+        }
+    }
+    
+    @availability(iOS, introduced=8.0)
+    public func getSharedPassword(account: String, completion: (password: String?, error: NSError?) -> () = { password, error -> () in }) {
+        if let domain = server.host {
+            requestSharedWebCredential(domain: domain, account: account) { (credentials, error) -> () in
+                if let credential = credentials.first {
+                    if let password = credential["password"] {
+                        completion(password: password, error: error)
+                    } else {
+                        completion(password: nil, error: error)
+                    }
+                } else {
+                    completion(password: nil, error: error)
+                }
+            }
+        } else {
+            let error = securityError(status: Status.Param.rawValue)
+            completion(password: nil, error: error)
+        }
+    }
+    
+    @availability(iOS, introduced=8.0)
+    public func setSharedPassword(password: String, account: String, completion: (error: NSError?) -> () = { e -> () in }) {
+        if let domain = server.host {
+            SecAddSharedWebCredential(domain, account, password) { error -> () in
+                if let error = error {
+                    completion(error: error.error)
+                } else {
+                    completion(error: nil)
+                }
+            }
+        } else {
+            let error = securityError(status: Status.Param.rawValue)
+            completion(error: error)
+        }
+    }
+    
+    @availability(iOS, introduced=8.0)
+    public func requestSharedWebCredential(completion: (credentials: [[String: String]], error: NSError?) -> () = { credentials, error -> () in }) {
+        requestSharedWebCredential(domain: nil, account: nil, completion: completion)
+    }
+    
+    @availability(iOS, introduced=8.0)
+    public func requestSharedWebCredential(#domain: String, completion: (credentials: [[String: String]], error: NSError?) -> () = { credentials, error -> () in }) {
+        requestSharedWebCredential(domain: domain, account: nil, completion: completion)
+    }
+    
+    @availability(iOS, introduced=8.0)
+    public func requestSharedWebCredential(#domain: String, account: String, completion: (credentials: [[String: String]], error: NSError?) -> ()) {
+        requestSharedWebCredential(domain: domain as String?, account: account as String?, completion: completion)
+    }
+    
+    private func requestSharedWebCredential(#domain: String?, account: String?, completion: (credentials: [[String: String]], error: NSError?) -> ()) {
+        SecRequestSharedWebCredential(domain, account) { (credentials, error) -> () in
+            var remoteError: NSError?
+            if let error = error {
+                remoteError = error.error
+                if remoteError?.code != Int(errSecItemNotFound) {
+                    println("error:[\(remoteError!.code)] \(remoteError!.localizedDescription)")
+                }
+            }
+            if let credentials = credentials as? [[String: AnyObject]] {
+                let credentials = credentials.map { credentials -> [String: String] in
+                    var credential = [String: String]()
+                    if let server = credentials[kSecAttrServer] as? String {
+                        credential["server"] = server
+                    }
+                    if let account = credentials[kSecAttrAccount] as? String {
+                        credential["account"] = account
+                    }
+                    if let password = credentials[kSecSharedPassword.takeUnretainedValue() as String] as? String {
+                        credential["password"] = password
+                    }
+                    return credential
+                }
+                completion(credentials: credentials, error: remoteError)
+            } else {
+                completion(credentials: [], error: remoteError)
+            }
+        }
+    }
+    
+    @availability(iOS, introduced=8.0)
+    public func generatePassword() -> String {
+        return SecCreateSharedWebCredentialPassword().takeUnretainedValue()
+    }
+    #endif
+    
     // MARK:
     
     public func set(value: String, key: String) -> NSError? {
@@ -757,11 +863,7 @@ extension Options {
                     &error
                 )
                 if let error = error?.takeUnretainedValue() {
-                    var code = CFErrorGetCode(error)
-                    var domain = CFErrorGetDomain(error)
-                    var userInfo = CFErrorCopyUserInfo(error)
-                    
-                    return (attributes, NSError(domain: domain, code: code, userInfo: userInfo))
+                    return (attributes, error.error)
                 }
                 if accessControl == nil {
                     let message = Status.UnexpectedError.description
@@ -1212,6 +1314,16 @@ extension FailableOf: Printable, DebugPrintable {
     }
 }
 
+extension CFError {
+    var error: NSError {
+        var domain = CFErrorGetDomain(self)
+        var code = CFErrorGetCode(self)
+        var userInfo = CFErrorCopyUserInfo(self)
+        
+        return NSError(domain: domain, code: code, userInfo: userInfo)
+    }
+}
+
 public enum Status : OSStatus {
     case Success
     case Unimplemented

+ 51 - 0
README.md

@@ -16,6 +16,7 @@ KeychainAccess is a simple Swift wrapper for Keychain that works on iOS and OS X
 - [Support accessibility](#accessibility)
 - [Support iCloud sharing](#icloud_sharing)
 - **[Support TouchID and Keychain integration (iOS 8+)](#touch_id_integration)**
+- **[Support Shared Web Credentials (iOS 8+)](#shared_web_credentials)**
 - Works on both iOS & OS X
 
 ## :book: Usage
@@ -366,6 +367,56 @@ if error != nil {
 }
 ```
 
+### <a name="shared_web_credentials"> :key: Shared Web Credentials
+
+> 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.  
+<https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/>
+
+```
+let keychain = Keychain(server: "https://www.kishikawakatsumi.com", protocolType: .HTTPS)
+
+let username = "kishikawakatsumi@mac.com"
+
+if let password = keychain.get(username) {
+    // If found password in the Keychain,
+    // then log into the server
+} else {
+    // If not found password in the Keychain,
+    // try to read from Shared Web Credentials
+    keychain.getSharedPassword(username) { (password, error) -> () in
+        if password != nil {
+            // If found password in the Shared Web Credentials,
+            // then log into the server
+            // and save the password to the Keychain
+
+            keychain[username] = password
+        } else {
+            // If not found password either in the Keychain also Shared Web Credentials,
+            // prompt for username and password
+
+            // Log into server
+
+            // If the login is successful,
+            // save the credentials to both the Keychain and the Shared Web Credentials.
+
+            keychain[username] = password
+            keychain.setSharedPassword(password, account: username)
+        }
+    }
+}
+```
+
+#### How to set up Shared Web Credentials
+
+> 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.
+
+> 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.
+
+> 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.
+
+**More details:**  
+<https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/>
+
 ### :key: Debugging
 
 #### Display all stored items if print keychain object