Simplifying using Keychain Access Groups

Keychain sharing is an easy and secure way to share information between apps on the same device. This is even more useful when you think about sharing information between an app and it’s extensions. In my opinion Keychain is just as easy as using NSUserDefaults but provides a greater level of security.

Although Keychain sharing is easy to use, I found the use of Access Groups or kSecAttrAccessGroup somewhat tricky.  There are a few different ways of working with Keychain Groups I thought it might be useful to go through the approach I adopted for others looking to implement Keychain sharing within their own apps.

First you will need to setup Keychain sharing in your app.  The below are some great tutorials to get you started.

Next, you will need to try to pass information between apps in your Keychain Group.  Here is where I ran into my problem.  At first I assumed that you simply set the kSecAttrAccessGroup value to the name of the Keychain Group you want to use.  No matter the KeyChain Group value I tried, it always returned a nil.

Why?  If you open your app’s entitlement file you can see why. Your keychain access group is a calculated value of your Keychain Group and your $(AppIdentifierPrefix).

<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.bencoding.awesome-group</string>
</array>
view raw entitlement.xml hosted with ❤ by GitHub

This means if you set kSecAttrAccessGroup to your keychain access group of com.bencoding.awesome-group it would always return nil. You would need to add the App ID Prefix (also called Team ID) to your Keychain Group. In our example this would be something like ABC1234DEF.com.bencoding.awesome-group.

Getting your App ID Prefix from the developer portal isn’t the end of the world and the tutorials at the beginning of this article do a good job covering why this is necessary.

What is another “magic” string in your app?

If you are building enterprise apps which are often resigned or just hate using “magic” strings this approach leaves you looking for an alternative.

Another approach is to use the Keychain’s own meta data find the App ID Prefix .  All you need to do is create a keychain item and then read the returned kSecAttrAccessGroup value from the meta data.  Using the provided kSecAttrAccessGroup value you can parse the  App ID Prefix and default Keychain Access Group.  Removing (or at least reducing) the need for “magic” strings.

I’ve encapsulated this approach into the KeyChainAccessGroupHelper.swift shown at the end of this post.

This approach makes using Keychain sharing with any of the popular Keychain libraries easier.  Below demonstrates how to use KeyChainAccessGroupHelper with the KeychainAccess library.

let info = KeyChainAccessGroupHelper.getAccessGroupInfo()
//Use the default Keychain group
let keychain = Keychain(service: "com.example.github-token", accessGroup: info.rawValue)
//Use the prefix to connect to another keychain group
let keychain = Keychain(service: "com.example.github-token", accessGroup: String(format: "%@.com.bencoding.awesome-group", info.prefix))
view raw example.swift hosted with ❤ by GitHub

The below gist shows the implementation details of KeyChainAccessGroupHelper.swift
//
// KeyStorage - Simplifying securely saving key information
//
// KeyChainAccessGroupHelper.swift
// Created by Ben Bahrenburg on 12/30/16.
// Copyright © 2017 bencoding.com. All rights reserved.
//
import Foundation
import Security
public struct KeyChainAccessGroupInfo {
public var prefix: String
public var keyChainGroup: String
public var rawValue: String
}
open class KeyChainAccessGroupHelper {
public class func getAccessGroupInfo() -> KeyChainAccessGroupInfo? {
let query: [String:Any] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : "detectAppIdentifierForKeyChainGroupIdUsage",
kSecAttrAccessible as String: kSecAttrAccessibleAlwaysThisDeviceOnly,
kSecReturnAttributes as String : kCFBooleanTrue
]
var dataTypeRef: AnyObject?
var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
if status == errSecItemNotFound {
let createStatus = SecItemAdd(query as CFDictionary, nil)
guard createStatus == errSecSuccess else { return nil }
status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
}
guard status == errSecSuccess else { return nil }
let accessGroup = ((dataTypeRef as! [AnyHashable: Any])[(kSecAttrAccessGroup as String)] as! String)
if accessGroup.components(separatedBy: ".").count > 0 {
let components = accessGroup.components(separatedBy: ".")
let prefix = components.first!
let elements = components.filter() { $0 != prefix }
let keyChainGroup = elements.joined(separator: ".")
return KeyChainAccessGroupInfo(prefix: prefix, keyChainGroup: keyChainGroup, rawValue: accessGroup)
}
return nil
}
}

KeyChainAccessGroupHelper is also used as part of the KeyStorage project.