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 |
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?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
SSKeychainQuery
's private methods to public header to make this subclass work.>>>
is a customflatMap
operator (well, sort of). It simply unwraps the optional value on the left and passes it to the function on the right if there's a value, and returns nil otherwise.