Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Custom SSKeychainQuery subclass that supports keychain sharing on OS X
// 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
@rinatkhanov

This comment has been minimized.

Copy link
Owner Author

commented Jun 30, 2015

  • You need to expose some of the SSKeychainQuery's private methods to public header to make this subclass work.
  • This code allows creating new items with custom Access Lists in the keychain, but not updating existing items (though there's a way).
  • >>> is a custom flatMap 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.
  • The code works, but it may be error-prone, so take it with a grain of salt.
@dvng88

This comment has been minimized.

Copy link

commented Aug 24, 2017

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
You can’t perform that action at this time.