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

时间:2016-11-16 17:12:03

标签: macos keychain launchd launch-daemon security-framework

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

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

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

SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &system_keychain);

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

SecKeychainSetUserInteractionAllowed(false);

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

OSStatus status = SecKeychainAddInternetPassword(
    system_keychain,
    urlLength, server_base_url,
    0, NULL,
    usernameLength, username,
    0, NULL,
    0,
    kSecProtocolTypeAny, kSecAuthenticationTypeAny,
    passwordLength, password,
    NULL);

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

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

status = SecKeychainFindInternetPassword(
    system_keychain,
    urlLength, url,
    0, NULL,
    usernameLength, username,
    0, NULL,
    0,
    kSecProtocolTypeAny, kSecAuthenticationTypeAny,
    &passwordLength, &password_data,
    NULL);

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

我们已经检查了一些其他细节以及我们尝试过的事情,但无济于事:

  • 守护程序二进制文件使用开发人员身份证书签名。
  • 守护程序二进制文件包含一个嵌入的Info.plist部分,其中包含一个包ID和版本。
  • 我可以在"中看到守护进程二进制文件;始终允许这些应用程序访问"列表在"访问控制" Keychain Access.app。中密码项的选项卡。
  • 如果我手动切换到"允许所有应用程序访问此项目"在Keychain Access中,它可以工作。然而,这在某种程度上违背了在钥匙串中保存密码的意义。
  • 我们尝试过使用SecKeychainAddInternetPassword的参数,但这似乎没有任何区别。
  • 我们尝试使用SecKeychainUnlock()明确解锁钥匙串,但正如文档所示,这似乎是多余的。
  • 正如您所期望的那样,删除Keychain Access.app中的项目会导致SecKeychainFindInternetPassword()产生errSecItemNotFound。{li>所以它绝对可以找到已保存的项目,但它不允许阅读。

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

有什么想法吗?

1 个答案:

答案 0 :(得分:8)

在这几天花了几个小时后,我们终于弄明白了发生了什么。

首先,我尝试构建一个可以重现问题的最小示例。 这并没有因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

这表明问题可能出在代码签名上。奇怪。使用codesign -vv检查二进制文件的代码签名没有任何问题。

在网络上搜索错误消息的各个部分之后,我找到了-67063 corresponds to 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/

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

我一做到这一点就行了!