首页 > 解决方案 > 如何通过命令行在 Xcode 中登录 Apple ID?

问题描述

我继承了一个 Xcode 设置,它构建了一个使用自动签名进行开发构建的 iOS 应用程序。我现在的任务是为这个项目构建一些 CI 设置,但不改变实际的 Xcode 项目。这意味着我现在无法切换到手动签名。

由于该项目在本地构建良好,我没想到这会成为一个大问题,但事实证明自动签名(显然,事后看来)需要您的 Xcode 登录到 Apple ID(Xcode => Preferences => Accounts ) 应该用于自动创建证书。

有没有办法通过命令行将 Apple ID 添加到 Xcode?


这是我已经做过的:

我已经环顾四周,但无法通过谷歌找到任何明显的答案。StackOverflow 上的所有问题和答案总是提到“只需快速打开 Xcode 并输入您的凭据”,遗憾的是这不适用于我们的 CI 设置。

我发现了这个 Jenkins “Xcode Plugin”,它可以让你导入一个.developerprofile你可以从 Xcode 导出的插件。但是我的 Java 真的很生疏,我无法完全理解这是否“仅”导入配置文件和身份,或者帐户列表。

玩弄.developerprofile我自己,它似乎将帐户信息(以及所有证书等)包含在一个.zip文件中,因此您可以提取文件。这也包括accounts.keychainand accounts.plist,但它们都用密码加密 - 我不知道如何使用它来获取真实数据以进一步调查。

如果您添加新的 Apple ID,我还试图找出 Xcode 最初保存信息的位置:它似乎将帐户名和密码以及一些令牌放入您的“登录”(com.apple.gs.xcode.auth.com.apple.account.AppleIDAuthentication.token)和“iCloud”钥匙串(Xcode-AlternateDSIDXcode-Token)中。我也无法手动重新创建钥匙串访问中的现有条目,因为“访问控制”->“此项目的访问组:”在手动创建应用程序密码时总是不同的。将项目复制到要导出的新钥匙串中也不起作用,因为iCloud钥匙串不允许我将东西复制到新钥匙串(即使在 iCloud 中禁用钥匙串同步后,钥匙串也被命名为“本地项目”) .

标签: iosxcodemacosterminal

解决方案


首先,我不确定您正在尝试做的事情是一个好主意。

请记住,如果您要将 Xcode 设置为在每次构建时自动请求 iOS 开发人员证书,并且该构建在不同的机器上执行(例如,托管 CI,例如 Travis 或 Azure Pipelines),您的 iOS 开发人员证书将被吊销并每次都重生。

一个更好的选择(在我看来)是通过您的开发人员配置文件导出您的 iOS 开发证书和配置文件,并在您的构建环境中导入开发证书和配置文件。然后,如果需要,更新您的 Xcode 项目以使用您刚刚导入的证书和配置文件。

我认为 Fastlane 已经可以做到几乎所有这些。如果你正在寻找灵感,Azure Pipelines 具有类似的功能。有一个任务是安装配置文件,一个是安装证书,另一个是构建 Xcode 项目并允许您覆盖在签署产品时使用的证书和配置文件


话虽如此,accounts.plistaccounts.keychain可能包含您正在寻找的信息。两个文件均使用 AES 加密进行加密。

用于加密文件的密钥是使用 PBKDF2(基于密码的密钥派生函数 2)从密码中派生的,使用以下参数:

  • 哈希函数:SHA256
  • 密码:您的密码
  • Salt:密码的字节表示,使用 UTF8 编码
  • 哈希迭代次数:33333
  • 密钥长度:10

“幻数”是 AppleSecKeyDeriveFromPassword函数使用的默认值,如此处所述并在此处实现

获得加密密钥后,您可以解密文件。您需要一个初始化向量 (IV),它也是 Apple 使用的默认值 - 一个 16 字节数组,完全由零组成。

在 C 中,您应该能够使用这样的代码来生成加密密钥:

CFStringRef password = CFSTR("verysecretstuff");
const char* saltBytes = CFStringGetCStringPtr(password, kCFStringEncodingUTF8);
CFDataRef salt = CFDataCreate(NULL, saltBytes, CFStringGetLength(password));

int keySizeInBits = kSecAES128;
CFNumberRef keySize = CFNumberCreate(NULL, kCFNumberIntType, &keySizeInBits);

int rounds = 33333;
CFNumberRef numberOfRounds = CFNumberCreate(NULL, kCFNumberIntType, &rounds);

CFMutableDictionaryRef parameters = CFDictionaryCreateMutable(NULL, 3, NULL, NULL);
CFDictionaryAddValue(parameters, kSecAttrKeyType, kSecAttrKeyTypeAES);
CFDictionaryAddValue(parameters, kSecAttrKeySizeInBits, keySize);
CFDictionaryAddValue(parameters, kSecAttrPRF, kSecAttrPRFHmacAlgSHA256);
CFDictionaryAddValue(parameters, kSecAttrRounds, numberOfRounds);
CFDictionaryAddValue(parameters, kSecAttrSalt, salt);

CFErrorRef error = NULL;
SecKeyRef key = SecKeyDeriveFromPassword(password, parameters, &error);

要解密数据,请使用:

const UInt *bytes = NULL; // Encrypted data
CFDataRef data = CFDataCreate(NULL, bytes, length);

CFErrorRef error = NULL;
SecTransformRef transform = SecDecryptTransformCreate(key, &error);
if ( transform == NULL )
{
    CFShow(error);
    CFRelease(error);
}

SecTransformSetAttribute(transform, kSecEncryptionMode, kSecModeCBCKey, &error);
SecTransformSetAttribute(transform, kSecPaddingKey, kSecPaddingPKCS7Key, &error);
SecTransformSetAttribute(transform, kSecTransformInputAttributeName, data, &error);
CFDataRef result = SecTransformExecute(transform, &error);

CFShow(result);

CFRelease(result);
CFRelease(data);
CFRelease(transform);

希望能帮助到你!


推荐阅读