以编程方式访问应用标识前缀

58

如何以编程方式访问Bundle Seed ID / Team ID / App Identifier前缀字符串?(据我所知,这些都是相同的东西)。

我正在使用UICKeychainStore密钥链包装器在多个应用程序之间保持数据。每个应用程序的entitlement plist中都有一个共享密钥链访问组,并共享相同的配置文件。默认情况下,密钥链服务使用plist中的第一个访问组作为保存数据的访问组。当我调试UICKeychainStore时,它看起来像“AS234SDG.com.myCompany.SpecificApp”。我想将访问组设置为“AS234SDG.com.myCompany.SharedStuff”,但似乎无法以编程方式获取访问组的“AS234SDG”字符串,希望避免硬编码。


我不确定我是否理解了你的意思,但你是否在寻找这个NSString *bundleIDStr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"]; ? - msk
2
这将返回“com.myCompany.SpecificApp” - 我正在寻找“AS234SDG”前缀。 - Jacob Jennings
哦,我现在明白你在问什么了... - msk
这是一个很好的问题。我也找不到这个。 - orip
5个回答

113

Info.plist可以包含您自己的信息,如果您在其中使用$(AppIdentifierPrefix)值,它会在构建阶段替换为实际的应用程序标识符前缀。

因此,请尝试以下操作:

在您的Info.plist文件中添加一个有关应用程序标识符前缀的信息。

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

你可以使用Objective-C编程语言以编程方式检索它:

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

并使用Swift:

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

请注意,appIdentifierPrefix 以句点结束;例如:AS234SDG.

5
这似乎是最佳的做法。您甚至可以将完整值存储为在授权中显示的内容,例如 $(AppIdentifierPrefix)com.MyCompany.MyApp,然后直接使用它而无需进行任何其他修改。 - Matej Bukovinski
同意这是首选。虽然编程方法被标记为答案可能在2012年就很好了,但我发现在2015年的iOS 8上,代码运行不一致。 - Mitch Cohen
2
这在我的XCode 5.1.1上不起作用。$(AppIdentifierPrefix)返回空白。在打包构建选项中启用预处理info.plist设置没有任何区别。 - Aidan
@CSmith 谢谢你让我知道。我已经提交了一个雷达报告。https://openradar.appspot.com/radar?id=4968408739217408 - Hiron
非常好,我也已经提交了一个错误报告,并提供了一个简单的样例应用程序给苹果重现这个问题。希望能够解决,我们在我们的环境中非常依赖它。 - CSmith
显示剩余4条评论

62

您可以通过查看现有KeyChain项的访问组属性(即kSecAttrAccessGroup)来编程方式地检索Bundle Seed ID。在下面的代码中,我查找现有的KeyChain条目并在不存在时创建一个。一旦我有了KeyChain条目,我就从中提取访问组信息,并将由“。”(句点)分隔的访问组的第一个组件作为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;
}

3
@RajPara:那只是我随意选择的一个值。如果你想的话,可以将其更改为“com.acme.bundleSeedID”。重点是创建一个条目在KeyChain中,并读取它并从访问组信息中提取捆绑种子ID。 - David H
1
@orange80: 你可以打开"embedded.mobileprovision"文件来提取应用程序ID。然而,由于它被封装在CMS结构中,并且CMS API在iOS上不是公开的,您可以使用以下选项提取plist有效负载:1)执行一些搜索和替换预处理以剥离CMS标头/签名,2)嵌入OpenSSL并使用其CMS API提取有效载荷,3)阅读CMS规范并自己构建有效负载提取器。此外,这里还有另一种可能的解决方案(我不知道它是否有效):https://dev59.com/33A75IYBdhLWcg3wlqAT#5240079 - David H
这个解决方案似乎不再起作用了 - 至少在模拟器中不适用于iOS 7。 - bluebamboo
@bluebamboo 在 Mac 上不存在访问组(因此在模拟器中不可用)。你尝试在设备上了吗? - Rob Napier
@DavidH 对于企业应用程序来说,无法保证嵌入在应用程序中的 embedded.mobileprovision 文件是实际用于配置您的应用程序的文件。有多种安装配置文件的方法。这为您提供了一种方法,在嵌入式配置文件过期后仍然可以继续使用应用程序。 - Bob Whiteman
显示剩余5条评论

14

这是 @David H 答案的 Swift 版本:

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
        }
    }

2
可以在Xcode 10 beta2上工作,但是从“Info.plist”中获取的“AppIdentifierPrefix”却为“nil”。 - scope

4
这是一个好问题,但为了实现你想要的目标,可能有一种解决方案不需要检索Bundle Seed ID。
从这篇 文章 中,关于你正在使用的相同的钥匙串包装器:
默认情况下,在写入时它将选择在你的Entitlements.plist中指定的第一个访问组,并且在未指定任何组时将搜索所有访问组。
然后将在授予权限的所有组中搜索密钥。因此,为了解决您的问题,您可以将所有捆绑应用程序的访问组添加到entitlements.plist中,而不是使用“共享内容”组,将$(CFBundleIdentifier)作为您的第一个钥匙串组(您的钥匙串包装器将在此组中编写),然后您就可以开始使用了。

3

如果在Xcode中搜索团队ID,您会发现此值托管在构建设置中的键DEVELOPMENT_TEAM下。

您可以通过将以下内容放入Info.plist文件来检索此键:

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

确保将此代码放入每个目标的Info.plist文件中,您可以使用此代码检索它:

let teamID = Bundle.main.infoDictionary!["DEVELOPMENT_TEAM"] as! String

这个解决方案可以在没有点后缀的情况下给出团队ID。

https://dev59.com/mmgt5IYBdhLWcg3w5xg_#28714850中的解决方案仅适用于从主应用程序目标获取团队ID。当尝试对共享扩展目标执行相同操作时,它将无法检索团队ID。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接