间歇和临时iOS钥匙串故障

GPR*_*yan 12 keychain ios swift pushkit callkit

我们有一个应用程序,它严重依赖于能够使用iOS访问用户的会话令牌Keychain.当我们的应用程序打开时,首先检查的是令牌是否可用 - 如果没有,我们会向用户显示登录屏幕.我们不使用任何第三方库,并使用Keychain's SecItemAdd()/ SecItemCopyMatching()直接使用以下选项:

  • kSecClassGenericPassword
  • kSecAttrAccessibleAlwaysThisDeviceOnly

在正常使用期间,我们看到很少甚至没有问题.

问题

我们已经有用户报告,在打开他们的应用程序时,他们会显示登录屏幕(建议Keychain无法找到值),当他们实际登录时.我们发现在这个例子中,我们发现在杀死和重新启动时该应用程序,用户恢复正常(会话被发现Keychain).一旦我们发现这一点,我们尝试添加一个指数退避来继续查询Keychain,认为它可能只在前几秒内不可用.这不起作用,并向我们证明Keychain似乎无法用于整个应用程序启动会话.我们无法重现此问题.它似乎非常间歇性地发生.

经过进一步调查,我们发现通常,用户在遇到此问题之前已收到VoIP推送.我们仍然无法可靠地重现这个问题,但是在调试时发现我们Keychain.session被发现是nil在接收这些推送时(我们也依赖它),这是在用户打开他们的应用程序之前看到他们是伪"退出".

我们使用PushKitPKPushRegistry进行VoIP推送.这要求我们的应用程序启用"后台模式:IP语音".我们按照Apple的文档建议使用所有这些.这是一个示例:

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
    guard let _ = Keychain.session else {
        print("Session token not available")
        return
    }
    handle(notification: payload.dictionaryPayload)
}
Run Code Online (Sandbox Code Playgroud)

此代码依赖于Keychain.session在接收VoIP推送时立即可用.如前所述,我们已经看到了它不可用的实例,因此这个函数退出并简单地返回.

我也发现了这个相关的Apple论坛帖子,暗示这可能是一个iOS错误.

有谁可以帮我们解决这个问题?任何帮助将不胜感激.即使这实际上是一个iOS错误,我们也可以采用解决方法.

Luc*_*aco 6

我认为这个话题长期以来一直在推动 iOS 开发社区疯狂。我已经阅读了太多关于此的文章和主题,以至于我数不清了。

所以,我也在处理这个问题,根据@naqi 的回答,我开始朝着这个方向前进,我可能已经找到了一个可行的解决方案。

假设

当应用程序因任何原因暂停时似乎会发生此问题(例如,应用程序在后台长时间处于后台并且由于内存压力,操作系统将其发送为暂停状态,或者应用程序处于 bg 并且用户升级到来自商店或自动应用程序更新的新版本这样做)。我不再完全确定挂起是什么意思,但我强烈怀疑应用程序可能会恢复/唤醒而没有实际显示在屏幕上(由于 bg 任务,但不一定)。Apple 开发者论坛上的一位 Apple 员工勇敢地回复说“尝试在addDidFinishLaunchingWithOptions和之间延迟appDidBecomeActive”。除了这里的疯狂之外,阅读该评论让我想到,也许当应用程序恢复时,它会触发addDidFinishLaunchingWithOptions但当然不是appDidBecomeActive (因为它可能不会由用户恢复,而是从操作系统在后台处理的任何类型的事件中恢复,我们可能没有控制似乎,或任何其他功能,如 bg 任务或静默推送通知等)。

额外信息

根据我在网上找到的一篇相当古老的渗透测试文章(https://resources.infosecinstitute.com/iphone-penetration-testing-3/)查询钥匙串实际上会将查询寻址到另一个称为securityd在设备上运行,它神奇地安排查询并将它们执行到钥匙串数据库,最后将内容返回给请求应用程序,假设当查询在钥匙串数据库上完成时应用程序仍然存在。这让我觉得,如果这仍然是真的,那么这样的过程可能属于“DDoS”攻击,您的应用程序(或者可能是所有正在执行的应用程序?不清楚它实际上是如何工作的)执行太多查询。这可能会导致误报错误,如 secItemNotFound 或类似错误。同样,由于没有人证实这一点,请将其作为我的个人考虑。

方法

从@Naqi 的回答中,我可以排除第 2,3 和 4 点,将第 1 点保留为唯一仍然有效的理由。说到这里,我决定将任何CRUD操作处理的钥匙扣,从addDidFinishLaunchingWithOptionsappDidBecomeActive,通过处理在那里的应用程序安装说明(无论需要由应用程序),以确保恢复行动韩元暂停的最终背景事件/appDidBecomeActive根本不会影响钥匙串,因为除非用户实际使应用程序打开并显示在屏幕上,否则不会被调用。当然,这应该只在应用启动时发生,所以你可以使用 bool 标志或类似的信号量方法appDidBecomeActive来确保你的设置逻辑只执行一次。

结合上述方法,在 App Delegate 中,我还application(shouldSaveApplicationState:)通过返回falseapplication(shouldRestoreApplicationState:仍然返回来明确实现false(即使我假设默认情况下应该已经是这样)。这可能不一定有用,但也值得一试。

总而言之,在一些随机访问钥匙串失败的受影响设备上,自几周以来似乎不再发生,考虑到这种情况几乎每 2/3 天发生一次,我认为它可能至少减轻了这个问题.

我可以说被监控的设备都处于不同的配置中,有很多应用程序安装与否,存储内存是否几乎已满,有很多应用程序在 bg 中或根本没有,任何形式的因素和不同的 iOS 版本。未启用到云的钥匙串备份,钥匙串上的项目属性至少为kSecAttrAccessibleWhenUnlockedThisDeviceOnly

未完全验证的开放点:据说对钥匙串的访问应该是线程安全的,但我发现网络上的不同线程相互矛盾。也许这会以某种方式影响您的逻辑并导致意外状态

恭喜你能读到这里:)

希望这有助于进一步了解 iOS 中的钥匙串悲剧是怎么回事


naq*_*aqi 4

我遇到了类似的问题,在与 WWDC 的工程师交谈后,我们发现了导致此行为的几个问题。

  1. 我们过于频繁地调用 Keychain,这可能会导致成本高昂且某些调用无法完成或超时的情况

  2. 如果应用程序没有为钥匙串提供访问组,或者没有将其添加到带有钥匙串的保存事务中,则 Xcode 会生成一个,但这是动态的,可以在开发环境之间更改

  3. 如果您在应用程序生命周期的后期提供了访问组,则您的旧钥匙串值将保持与该应用程序关联,并且将使用您现在提供的访问组创建一个新值
  4. 这也意味着当您查询钥匙串时,您必须提供一个访问组,否则您将获得对首先找到的内容的引用。

编辑:格式化