【问题标题】:Mac Launch Daemon unable to retrieve password from system keychain after saving it thereMac Launch Daemon 在将密码保存在那里后无法从系统钥匙串中检索密码
【发布时间】:2017-03-31 00:15:20
【问题描述】:

我们有一个启动守护程序,它(出于各种原因必然)以 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”keychain中看到item了。

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

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

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

我们检查了一些额外的细节和我们尝试过的事情,但无济于事:

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

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

有什么想法吗?

【问题讨论】:

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


    【解决方案1】:

    在这几天花费了更多时间之后,我们终于弄清楚了发生了什么。

    首先,我尝试构建一个可以重现该问题的最小示例。 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之前删除目标文件。

    只要我这样做,它就起作用了!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-29
      • 2012-08-05
      • 1970-01-01
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 2018-09-04
      • 2017-09-05
      相关资源
      最近更新 更多