First, it's important to realize that Xcode is already adding the AppIdentifierPrefix to your identifier. It unfortunately hides it in the GUI, but if you open the entitlements plist, you'll see it. This is the identifier that is used to sign the app, and it's the piece that is used to enforce access control. I don't believe the teamid
prefix you're adding really does anything. I generally would recommend an access group com.x.shared
or com.x.appgroup.shared
and not use com.x.z
(I'm assuming com.x.y
already exists, so you can't change that).
I'm assuming here that you don't want to have to force users to upgrade App1, correct? I'm moving forward on that assumption.
If you can upgrade App1 (not require an upgrade, but make sure that all new customers have an upgraded version), then only store in com.x.y
if it exists. Otherwise, store in com.x.shared
:
- When you read from the keychain, don't use an access group. This will get the first matching record.
- When you write to the keychain, use the access group that was in the record you read.
If you don't want to upgrade App1 at all right now (required or not), then just always read and write to com.x.y
in App2.
When you're ready to end-of-life the com.x.y
group (if you're able to finally upgrade all App1 supported users), then you can switch to:
- Read from
com.x.y
. If it's found, delete it, and recreate it as com.x.shared
. You can do this one-time in the application startup (just write an NSUserDefaults
that says you've done it.
- From then on, always use
com.x.shared
explicitly.
The key tool here is that when you ask for an explicitly access group, you have to provide the whole thing, including you AppId (which isn't displayed in the Xcode GUI). You can of course hard-code it, but a better solution is to dynamically query it. I use an updated version of David H's code:
- (NSString *)bundleSeedID {
NSDictionary *query = @{ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : @"bundleSeedIDQuery",
(__bridge id)kSecAttrService : @"",
(__bridge id)kSecReturnAttributes : (id)kCFBooleanTrue
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFTypeRef)query,
(CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((__bridge CFTypeRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess)
return nil;
NSString *accessGroup = [(__bridge NSDictionary *)result
objectForKey:(__bridge id)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:@"."];
NSString *bundleSeedID = components[0];
CFRelease(result);
return bundleSeedID;
}
This will tell you your prefix at runtime. It does so b creating a bogus keychain entry, then querying it and seeing what access group was attached to it.
You may be interested in the first section of Getting Security and Privacy Right from Renaissance.io 2014. You can skip to "Protecting Secrets with Keychain."