Access App Identifier Prefix programmatically

27,186

Solution 1

You can programmatically retrieve the Bundle Seed ID by looking at the access group attribute (i.e. kSecAttrAccessGroup) of an existing KeyChain item. In the code below, I look up for an existing KeyChain entry and create one if it doesn't not exist. Once I have a KeyChain entry, I extract the access group information from it and return the access group's first component separated by "." (period) as the Bundle Seed ID.

+ (NSString *)bundleSeedID {
    NSString *tempAccountName = @"bundleSeedID";
    NSDictionary *query = @{
        (__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassGenericPassword,
        (__bridge NSString *)kSecAttrAccount : tempAccountName,
        (__bridge NSString *)kSecAttrService : @"",
        (__bridge NSString *)kSecReturnAttributes: (__bridge NSNumber *)kCFBooleanTrue,
    };
    CFDictionaryRef result = nil;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
    if (status == errSecItemNotFound)
        status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
    if (status != errSecSuccess) {
        return nil;
    }
    status = SecItemDelete((__bridge CFDictionaryRef)query); // remove temp item
    NSDictionary *dict = (__bridge_transfer NSDictionary *)result;
    NSString *accessGroup = dict[(__bridge NSString *)kSecAttrAccessGroup];
    NSArray *components = [accessGroup componentsSeparatedByString:@"."];
    NSString *bundleSeedID = [[components objectEnumerator] nextObject];
    return bundleSeedID;
}

Solution 2

Info.plist can have your own information and if you write a value with $(AppIdentifierPrefix), it is replaced to the real app identifier prefix at building phase.

So, try this:

In your Info.plist, add an info about app identifier prefix.

<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>

You can then retrieve it programmatically with Objective-C:

NSString *appIdentifierPrefix =
    [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppIdentifierPrefix"];

and with Swift:

let appIdentifierPrefix =
    Bundle.main.infoDictionary!["AppIdentifierPrefix"] as! String

Note that appIdentifierPrefix ends with a period; e.g. AS234SDG.

Solution 3

Here is the Swift version of @David H answer:

static func bundleSeedID() -> String? {
        let queryLoad: [String: AnyObject] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: "bundleSeedID" as AnyObject,
            kSecAttrService as String: "" as AnyObject,
            kSecReturnAttributes as String: kCFBooleanTrue
        ]

        var result : AnyObject?
        var status = withUnsafeMutablePointer(to: &result) {
            SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
        }

        if status == errSecItemNotFound {
            status = withUnsafeMutablePointer(to: &result) {
                SecItemAdd(queryLoad as CFDictionary, UnsafeMutablePointer($0))
            }
        }

        if status == noErr {
            if let resultDict = result as? [String: Any], let accessGroup = resultDict[kSecAttrAccessGroup as String] as? String {
                let components = accessGroup.components(separatedBy: ".")
                return components.first
            }else {
                return nil
            }
        } else {
            print("Error getting bundleSeedID to Keychain")
            return nil
        }
    }

Solution 4

This is a good question but to achieve what you were intended to do, there could have been a solution that does not require to retrieve the Bundle Seed ID.

From this article, about the same keychain wrapper you're using:

By default it will pick the first access-group specified in your Entitlements.plist when writing and will search across all access-groups when none is specified.

The key will then be search in all groups where access is granted. So to solve your problem, you could add access group of all your bundle apps into your entitlements.plist instead of using a "shared stuff" group, put $(CFBundleIdentifier) as your first keychain group (your keychain wrapper will then write in this group) and you're all set

Share:
27,186
Jacob Jennings
Author by

Jacob Jennings

iOS Development

Updated on July 09, 2022

Comments

  • Jacob Jennings
    Jacob Jennings almost 2 years

    How can I access the Bundle Seed ID/Team ID/App Identifier Prefix string programmatically? (These are all the same thing as far as I can tell).

    I am using the UICKeychainStore keychain wrapper to persist data across several applications. Each of these applications has a shared keychain access group in their entitlement plists, and share the same provisioning profile. By default, the keychain services use the first access group in the plist as the access group to save data to. This looks like "AS234SDG.com.myCompany.SpecificApp" when I debug UICKeychainStore. I would like to set the access group to "AS234SDG.com.myCompany.SharedStuff", but I can't seem to locate how to get the "AS234SDG" string of the access group programmatically, and would like to avoid hard-coding it if possible.