简介

最近更新了 macOS 10.15,sidecar(官方中文叫随航)确实好用,不过我在闲逛官网的时候发现了另外一个功能,如题图所示。 实际测试了一下,效果确实好使。

问题有二:

  • 可以让 sudo 也支持这个功能吗?
  • Apple Watch 时不时无法连接,需要重启才能连回,有办法检测 Apple Watch 的连接状态吗?

初探

翻看了一些资料,找到了之前使用 Touch ID 鉴权 sudo 的方法: 在 /etc/pam.d/sudo 中加入 auth sufficient pam_tid.so。 那么这一招对 Apple Watch 是否管用?

一开始,我的想法是,会不会有类似 pam_watch.so 的 pam 文件?

macOS 的默认 pam 文件夹在 /usr/lib/pam,看了看,里面并没有新增什么东西。

我们首先按照老方法直接加入,经过测试之后发现,当拉起 Touch ID 验证窗时,手表也会收到鉴权提示,证明此方法有效。

那么就这么解决了吗?并没有。

当电脑合盖连显示器之后,这个鉴权就失效了,回退到原本的密码输入中,想解决这个问题,还需要进一步探究原理。

macOS 的鉴权机制

macOS 有两套鉴权机制,一套是 pam.d ,另一套则是 LocalAuthentication.framework。

查看源码后我们得知,pam_tid是对后者的一个封装。 https://opensource.apple.com/source/pam_modules/pam_modules-173.50.1/modules/pam_tid/pam_tid.c.auto.html

也就是 sudo -> pam_tid.so -> LocalAuthentication.framework -> 指纹或手表验证,这样一条调用链

观察到这一段:

    /* evaluate policy */
    if (!LAEvaluatePolicy(context, kLAPolicyDeviceOwnerAuthenticationWithBiometrics, options, &error)) {
        // error is intended as failure means Touch ID is not usable which is in fact not an error but the state we need to handle
        if (CFErrorGetCode(error) != kLAErrorNotInteractive) {
            os_log_debug(PAM_LOG, "policy evaluation failed: %ld", CFErrorGetCode(error));
            retval = PAM_AUTH_ERR;
            goto cleanup;
        }
    }

该段代码执行 LAEvaluatePolicy 调起生物识别。 找一下 macOS 10.15 的 SDK 头文件:

// LocalAuthentication.framework/Versions/A/Headers/LAPublicDefines.h
#define kLAPolicyDeviceOwnerAuthenticationWithBiometrics        1
#define kLAPolicyDeviceOwnerAuthentication                      2
#define kLAPolicyDeviceOwnerAuthenticationWithWatch             3
#define kLAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch 4

果不其然,相比 10.14 新增了 WatchBiometricsOrWatch

那是什么原因导致我们合盖就无法鉴权的呢?

猜测一:Apple Watch 的鉴权和 Touch ID 是同时进行的(and),当 Touch ID 无法执行时(即合上电脑),Apple Watch 也会返回无法执行。

做个实验:

BOOL can = [context canEvaluatePolicy:kLAPolicyDeviceOwnerAuthenticationWithWatch error:&error];
LABiometryType bioType = context.biometryType;
NSLog(@"%hhd",can);
枚举 状态 返回值
kLAPolicyDeviceOwnerAuthenticationWithWatch 开盖 1
kLAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch 开盖 1
kLAPolicyDeviceOwnerAuthenticationWithWatch 合盖 1
kLAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch 合盖 1

证明我们猜测是错误的,而且,用 IDA 打开新版的 pam_tid.so 发现,调用的参数竟然不是 BiometricsOrWatch , 而仍是之前的 Biometric

if ( (unsigned __int8)LAEvaluatePolicy(v33, 1LL, v31, &v65) || CFErrorGetCode(v65) == -1004 )
                                            ~~~

这是怎么一回事?按照苹果的说法,只有 BiometricsOrWatch 才能实现若指纹识别无法使用的情况下使用Watch。

猜测二:kLAPolicyDeviceOwnerAuthenticationWithBiometrics 的真实表现型为 “BiometricAndWatch”,即猜测一,但 Apple Watch 无法检测的话不影响 Touch ID。

打开 IDA,找到 pam_tid.so,跳转到地址 1FBB,此处语句为 mov esi,1,也就是kLAPolicyDeviceOwnerAuthenticationWithBiometrics的枚举值。

选择 Edit - Patch Program - Patch Word,将 0x1BE 改为 0x4BE(即kLAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch的枚举值)

然后选择 Apply patches to input file,得到新的 pam_tid.so。

由于pam文件经过签名,我们如果直接使用这个文件,会被 Kill 掉,使用以下语句进行签名:

sudo codesign -f -s pam_tid.so

其中, -f 代表替换原签名,-s代表使用 Ad-Hoc 签名。

由于 macOS 的 rootless 机制,我们无法修改 /usr/lib/pam。所以签名完成后,你需要找一个文件夹放置,然后重新将 /etc/pam.d/sudo 里面的so文件指向新的so文件。

测试后发现,无论是 Touch ID, 还是 Apple Watch,均能完成鉴权,且在屏幕合盖后,Apple Watch 也能进行独立鉴权。

不过,有一个缺点,Apple Watch 总是断连,这个问题不知道如何解决。

检测 Apple Watch 与 macOS 的连接状态

LAContext* context = [[LAContext alloc]init];
NSLog(@"%hhd",[context canEvaluatePolicy:kLAPolicyDeviceOwnerAuthenticationWithWatch error:&error]);

当 Apple Watch 与 macOS 连接正常时,该值返回 1。问题在于,第一次要求鉴权时,Apple Watch 总是不可用(即返回0),需要等待较久时间才能自动连回电脑。

而 macOS 自带的使用 Apple Watch 解锁基本上每次都能成功,准确地说:

  • 运行上述代码,显示 0。
  • 锁定 macOS。
  • 唤醒 macOS,手表自动解锁。
  • 再次运行上述代码,显示 1。

暂时不知道如何实现的,留作以后探究吧。

结论

由于未知的原因,系统自带的 pam_tid.so 仍然没有更新,而 LAContextcanEvaluatePolicy 方法将 kLAPolicyDeviceOwnerAuthenticationWithBiometrics 的默认表现型改为当 Touch ID 无法执行时(即合上电脑),Apple Watch 也会返回无法执行。

以上结论基于实验与猜测,附上 pam_tid.so 的修改版。