Last active
August 24, 2017 13:06
-
-
Save rinatkhanov/a837f1e53c3f921db131 to your computer and use it in GitHub Desktop.
Custom SSKeychainQuery subclass that supports keychain sharing on OS X
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// KeychainQuery is OS X only. | |
#if os(OSX) | |
/// Custom SSKeychainQuery subclass that supports keychain sharing on OS X. | |
/// based on https://developer.apple.com/library/mac/documentation/Security/Conceptual/keychainServConcepts/03tasks/tasks.html#//apple_ref/doc/uid/TP30000897-CH205-BBCHEABI (Listing 3-2) | |
private class KeychainQuery: SSKeychainQuery { | |
/// Specify app paths to share keychain with. | |
/// No need to pass current app's path (which calls the code). | |
var sharedAppPaths: [String]? | |
override func save(errorPtr: NSErrorPointer) -> Bool { | |
if let sharedAppPaths = sharedAppPaths where sharedAppPaths.count > 0 { | |
if service == nil || account == nil || password == nil { | |
addError(KeychainQuery.errorWithCode(SSKeychainErrorCode.BadArguments.rawValue), toPointer: errorPtr) | |
return false | |
} | |
let searchQuery = query() | |
var status = SecItemCopyMatching(searchQuery, nil) | |
switch status { | |
case errSecSuccess: | |
consoleLog("|Keychain| Item already present, just updating the value.") | |
status = SecItemUpdate(searchQuery, [(kSecValueData as! String): passwordData]) | |
case errSecItemNotFound: // create new then | |
let (access, accStatus) = createAccess() | |
status = (access >>> addItemWithAccess) ?? accStatus | |
default: break | |
} | |
let error = KeychainQuery.errorWithCode(status) | |
addError(error, toPointer: errorPtr) | |
return error == nil | |
} else { | |
// fallback to original implementation if no paths specified | |
return super.save(errorPtr) | |
} | |
} | |
private func createAccess() -> (SecAccess?, OSStatus) { | |
consoleLog("|Keychain| Creating custom access list") | |
var status = noErr | |
var apps = [Unmanaged<SecTrustedApplication>?](count: (sharedAppPaths?.count ?? 0) + 1, repeatedValue: nil) | |
status = SecTrustedApplicationCreateFromPath(nil, &apps[0]) // add this app | |
if let sharedAppPaths = sharedAppPaths { | |
for (index, path) in enumerate(sharedAppPaths) { | |
if status != noErr { break } | |
status = SecTrustedApplicationCreateFromPath(path, &apps[index + 1]) | |
} | |
} | |
var access: Unmanaged<SecAccess>? | |
if status == noErr { | |
var appsPointer = UnsafeMutablePointer<UnsafePointer<Void>>(apps.map { $0!.takeUnretainedValue() }) | |
let cfArr = CFArrayCreate(nil, appsPointer, apps.count, nil) | |
status = SecAccessCreate(service, cfArr, &access) | |
} | |
return (access?.takeUnretainedValue(), status) | |
} | |
private func addItemWithAccess(access: SecAccess) -> OSStatus { | |
consoleLog("|Keychain| Creating item with custom ACL.") | |
var attrs: [SecKeychainAttribute] = [] | |
createAttribute(kSecLabelItemAttr, label) >>> attrs.append | |
createAttribute(kSecAccountItemAttr, account) >>> attrs.append | |
createAttribute(kSecServiceItemAttr, service) >>> attrs.append | |
var attrList = SecKeychainAttributeList(count: UInt32(attrs.count), attr: &attrs) | |
let pass = (password as NSString).UTF8String | |
let len = UInt32(truncatingBitPattern: strlen(pass)) | |
return SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attrList, len, pass, nil, access, nil) | |
} | |
private func createAttribute(tag: Int, _ data: String?) -> SecKeychainAttribute? { | |
if var data = data { | |
let utfString = UnsafeMutablePointer<Int8>((data as NSString).UTF8String) | |
let len = UInt32(truncatingBitPattern: strlen(utfString)) | |
return SecKeychainAttribute(tag: SecKeychainAttrType(tag), length: len, data: utfString) | |
} else { | |
return nil | |
} | |
} | |
private func addError(error: NSError?, toPointer pointer: NSErrorPointer) { | |
if let error = error where pointer != nil { | |
pointer.memory = error | |
} | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, I am getting too many errors. 'SSKeychainQuery' is it Objective-C file, I am trying with that and no luck. Any suggestion? Also I am trying with Xcode 8.3 and Swift3, is it correct?