Mac 启动守护程序在保存密码后无法从系统钥匙串中检索密码
Mac Launch Daemon unable to retrieve password from system keychain after saving it there
我们有一个 Launch Daemon(出于各种原因,必须)以 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.
中可以看到"system"钥匙串中的物品
在我们的守护程序的后续运行中检索它是通过以下行完成的:
status = SecKeychainFindInternetPassword(
system_keychain,
urlLength, url,
0, NULL,
usernameLength, username,
0, NULL,
0,
kSecProtocolTypeAny, kSecAuthenticationTypeAny,
&passwordLength, &password_data,
NULL);
不幸的是,由于我们不清楚的原因,它已经开始返回 errSecAuthFailed
。
我们检查过的一些额外细节和我们尝试过的东西都无济于事:
- 守护程序二进制文件使用 Developer Id 证书签名。
- 守护程序二进制文件包含一个带有捆绑 ID 和版本的嵌入式 Info.plist 部分。
- 我可以在 Keychain Access.app 密码项的 "Access Control" 选项卡的 "Always allow access by these applications" 列表中看到守护程序二进制文件 Access.app。
- 如果我在 Keychain Access 中手动切换到 "Allow all applications to access this item",就可以了。然而,这在某种程度上违背了将密码保存在钥匙串中的意义。
- 我们试过
SecKeychainAddInternetPassword
的参数,但这似乎没有任何区别。
- 我们已经尝试使用
SecKeychainUnlock()
显式解锁钥匙串,但正如文档所建议的那样,这似乎是多余的。
- 如您所料,删除
Keychain Access.app
中的项目会导致 SecKeychainFindInternetPassword()
产生 errSecItemNotFound
。所以它肯定可以找到保存的项目,只是不允许读取它。
钥匙串文档并不十分容易阅读,并且部分内容相当重复。 ("In order to do Y, you need to do Y," 没有提到你为什么想做 Y。)不过,我认为我已经完成并理解了大部分内容。我们特定设置的各个方面没有详细介绍(从守护程序访问),但很明显访问以前由同一应用程序保存的项目不需要任何特殊授权或认证。这与我们看到的行为直接矛盾。
有什么想法吗?
在这几天里又花了几个小时之后,我们终于搞清楚了是怎么回事。
首先,我尝试构建一个可以重现问题的最小示例。 这并没有因为 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
。评论是"code identity has been invalidated."
好吧,肯定是一些代码签名错误,但这是什么意思,为什么会发生?
再四处寻找终于找到了解释和解决方案:http://lists.apple.com/archives/apple-cdsa/2010/Mar/msg00027.html
It means that at some point since the program got started, something
happened to it that made it invalid.
和
if you run a signed program, then replace it (by, say, building a
new version in place :-), and then run the new version, the kernel
will still hold the old signature attached to the executable's vnode.
If that's your situation, just removing the executable and recreating
it clears up the problem for good (until you overwrite the file again
:-). We recommend that signed code always be replaced (mv(1), not
cp(1), or equivalents).
这解释了。我正在使用
将新版本的守护程序复制到位
sudo cp path/to/built/daemon /usr/local/libexec/
显然,这会就地覆盖文件,而不是创建一个新的 vnode,写入它,然后在旧文件上重命名它。所以解决方案是先 cp
到临时目录,然后 mv
到位。或者在使用 cp
.
之前删除目标文件
我一这样做,就奏效了!
我们有一个 Launch Daemon(出于各种原因,必须)以 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.
中可以看到"system"钥匙串中的物品在我们的守护程序的后续运行中检索它是通过以下行完成的:
status = SecKeychainFindInternetPassword(
system_keychain,
urlLength, url,
0, NULL,
usernameLength, username,
0, NULL,
0,
kSecProtocolTypeAny, kSecAuthenticationTypeAny,
&passwordLength, &password_data,
NULL);
不幸的是,由于我们不清楚的原因,它已经开始返回 errSecAuthFailed
。
我们检查过的一些额外细节和我们尝试过的东西都无济于事:
- 守护程序二进制文件使用 Developer Id 证书签名。
- 守护程序二进制文件包含一个带有捆绑 ID 和版本的嵌入式 Info.plist 部分。
- 我可以在 Keychain Access.app 密码项的 "Access Control" 选项卡的 "Always allow access by these applications" 列表中看到守护程序二进制文件 Access.app。
- 如果我在 Keychain Access 中手动切换到 "Allow all applications to access this item",就可以了。然而,这在某种程度上违背了将密码保存在钥匙串中的意义。
- 我们试过
SecKeychainAddInternetPassword
的参数,但这似乎没有任何区别。 - 我们已经尝试使用
SecKeychainUnlock()
显式解锁钥匙串,但正如文档所建议的那样,这似乎是多余的。 - 如您所料,删除
Keychain Access.app
中的项目会导致SecKeychainFindInternetPassword()
产生errSecItemNotFound
。所以它肯定可以找到保存的项目,只是不允许读取它。
钥匙串文档并不十分容易阅读,并且部分内容相当重复。 ("In order to do Y, you need to do Y," 没有提到你为什么想做 Y。)不过,我认为我已经完成并理解了大部分内容。我们特定设置的各个方面没有详细介绍(从守护程序访问),但很明显访问以前由同一应用程序保存的项目不需要任何特殊授权或认证。这与我们看到的行为直接矛盾。
有什么想法吗?
在这几天里又花了几个小时之后,我们终于搞清楚了是怎么回事。
首先,我尝试构建一个可以重现问题的最小示例。 这并没有因为 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
。评论是"code identity has been invalidated."
好吧,肯定是一些代码签名错误,但这是什么意思,为什么会发生?
再四处寻找终于找到了解释和解决方案:http://lists.apple.com/archives/apple-cdsa/2010/Mar/msg00027.html
It means that at some point since the program got started, something happened to it that made it invalid.
和
if you run a signed program, then replace it (by, say, building a new version in place :-), and then run the new version, the kernel will still hold the old signature attached to the executable's vnode. If that's your situation, just removing the executable and recreating it clears up the problem for good (until you overwrite the file again :-). We recommend that signed code always be replaced (mv(1), not cp(1), or equivalents).
这解释了。我正在使用
将新版本的守护程序复制到位sudo cp path/to/built/daemon /usr/local/libexec/
显然,这会就地覆盖文件,而不是创建一个新的 vnode,写入它,然后在旧文件上重命名它。所以解决方案是先 cp
到临时目录,然后 mv
到位。或者在使用 cp
.
我一这样做,就奏效了!