Skip to content

Instantly share code, notes, and snippets.

@rinatkhanov
Last active August 24, 2017 13:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rinatkhanov/a837f1e53c3f921db131 to your computer and use it in GitHub Desktop.
Save rinatkhanov/a837f1e53c3f921db131 to your computer and use it in GitHub Desktop.
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
Copy link
Author

  • 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
Copy link

dvng88 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