Mac启动守护程序在将其保存后无法从系统钥匙串中检索密码

pmd*_*mdj 5 macos keychain security-framework launchd launch-daemon

我们有一个启动守护进程(必然由于各种原因)以root身份运行,并通过网络与服务器组件通信.它需要对服务进行身份验证,因此在首次获取密码时,我们会将其保存到系统密钥链中.在随后的发布中,我们的想法是从密钥链中检索密码并使用它来验证网络服务.

这一直运行良好,但在macOS 10.12上,现有代码停止工作,我们已经完全不知道如何解决这个问题.归结为:

无论我们是保存新密码还是检索旧密码,我们都会使用以下方法获取对系统密钥链的引用:

SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &system_keychain);
Run Code Online (Sandbox Code Playgroud)

我们还禁用了用户交互以获得良好的衡量标准,尽管我们希望它在守护进程的上下文中已经关闭.

SecKeychainSetUserInteractionAllowed(false);
Run Code Online (Sandbox Code Playgroud)

将新密码保存到钥匙串时,我们会使用

OSStatus status = SecKeychainAddInternetPassword(
    system_keychain,
    urlLength, server_base_url,
    0, NULL,
    usernameLength, username,
    0, NULL,
    0,
    kSecProtocolTypeAny, kSecAuthenticationTypeAny,
    passwordLength, password,
    NULL);
Run Code Online (Sandbox Code Playgroud)

这很有用.报告成功,我可以在Keychain Access.app的"系统"钥匙串中看到该项目.

在我们的守护进程的后续运行中检索它是通过以下行完成的:

status = SecKeychainFindInternetPassword(
    system_keychain,
    urlLength, url,
    0, NULL,
    usernameLength, username,
    0, NULL,
    0,
    kSecProtocolTypeAny, kSecAuthenticationTypeAny,
    &passwordLength, &password_data,
    NULL);
Run Code Online (Sandbox Code Playgroud)

不幸的是,errSecAuthFailed由于我们不清楚的原因,这已经开始返回.

我们检查了一些额外的细节以及我们尝试过的东西,但无济于事:

  • 守护程序二进制文件使用Developer Id证书进行签名.
  • 守护程序二进制文件包含一个嵌入式Info.plist部分,其中包含一个包ID和版本.
  • 我可以在Keychain Access.app中密码项的"访问控制"选项卡中的"始终允许这些应用程序访问"列表中看到守护进程二进制文件.
  • 如果我在Keychain Access中手动切换到"允许所有应用程序访问此项",则可以正常工作.然而,这在某种程度上违背了在钥匙串中保存密码的观点.
  • 我们已经尝试过使用参数SecKeychainAddInternetPassword,但这似乎没有任何区别.
  • 我们已尝试使用明确解锁钥匙链SecKeychainUnlock(),但正如文档所示,这似乎是多余的.
  • 删除项Keychain Access.app原因SecKeychainFindInternetPassword()产生errSecItemNotFound,正如你所期望的.所以它绝对可以找到保存的项目,它不允许读取它.

钥匙串文档不是很容易阅读,而是部分相当重复.("为了做Y,你需要做Y",但没有提到为什么你想要做Y.)尽管如此,我认为我已经完成并理解了大部分内容.我们特定设置的各个方面都没有详细介绍(从守护程序访问),但似乎很清楚,访问以前由同一个应用程序保存的项目不需要任何特殊授权或身份验证.这与我们所看到的行为直接矛盾.

有任何想法吗?

pmd*_*mdj 9

在这几天花了几个小时后,我们终于弄清楚了发生了什么.

首先,我尝试构建一个可以重现问题的最小示例.这没有失败,errSecAuthFailed因此没有重现问题.所以回到原来的守护进程,必须有一些特别关于它的东西出错了.

接下来的想法是检查系统日志中的SecKeychainFindInternetPassword()调用时间.这出现了一些错误消息:

securityd   CSSM Exception: -2147411889 CSSMERR_CL_UNKNOWN_TAG
securityd   MacOS error: -67063
securityd   MacOS error: -67063
securityd   code requirement check failed (-67063), client is not Apple-signed
securityd   CSSM Exception: 32 CSSM_ERRCODE_OPERATION_AUTH_DENIED
OurDaemon   subsystem: com.apple.securityd, category: security_exception, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 2, enable_private_data: 0
OurDaemon   CSSM Exception: -2147416032 CSSMERR_CSP_OPERATION_AUTH_DENIED
Run Code Online (Sandbox Code Playgroud)

这表明问题可能出在代码签名上.奇怪.检查二进制代码签名并codesign -vv返回没有问题.

在网络上搜索错误消息的各个部分之后,我发现-67063对应了errSecCSGuestInvalid.评论内容为"代码标识已失效".

好的,肯定是一些代码签名错误,但它是什么意思,为什么会发生?

围绕更多的狩猎终于出现了解释,还有解决方案:http://lists.apple.com/archives/apple-cdsa/2010/Mar/msg00027.html

这意味着在程序启动后的某个时刻,它发生了一些使它无效的事情.

如果你运行一个签名的程序,然后替换它(比如说,建立一个新的版本:-),然后运行新版本,内核仍将保留附加到可执行文件的vnode的旧签名.如果这是你的情况,只需删除可执行文件并重新创建它就可以彻底清除问题(直到你再次覆盖文件:-).我们建议始终替换签名代码(mv(1),而不是cp(1)或等效代码).

这解释了它.我正在使用将新版本的守护进程复制到位

sudo cp path/to/built/daemon /usr/local/libexec/
Run Code Online (Sandbox Code Playgroud)

显然,它会就地覆盖文件而不是创建新的vnode,编写它,然后在旧文件上重命名.所以解决方案是首先cp到临时目录,然后mv到位.或者在使用前删除目标文件cp.

我一做到这一点就行了!