本文的内容基于https://nelenkov.blogspot.com/2014/12/dissecting-lollipops-smart-lock.html,省略去NFC的内容。
Android5.0已经发布有一段时间了,其大部分特性已经被介绍过,其中包含了一系列的安全增强特性,当中磁盘加密功能获得了大部分的关注。Smart Lock(最初在2014年Google I/O大会引入)作为安全增强特性当中一种在特定环境条件下允许锁屏直接解锁的特性,是最与用户体验直接相关的。因此,该功能被广泛讨论和记录在博客上。然而,作为Google Play服务内置的专属功能,Smart Lock的实现和安全等级还未对外公开。此文会深入探究Smart Lock所在的Android Framework扩展部分,展示如何创建你自己的解锁方法以及简要地讨论其大致实现。
Smart Lock基于Android L的被称为信任代理(trust agents)的新特性。引用自相关文档,信任代理是一个”告知系统当前设备所处环境是否被信任“的服务。信任的具体先决条件由信任代理定义。当一个信任代理认为它可以信任当前环境,它会通过回调来告知系统,系统会决定如何去绕过设备的安全配置。当前Android的一个典型的范例是:当处于一个被信任的环境时,系统会授予用户绕过锁屏的能力。
信任可以授予给不同的用户,所以不同用户的信任代理可以被加以区别地配置。另外,信任可以被授予一段时间,当超过这段时间以后就会重置成不信任状态。设备的管理员可以为信任代理设置其允许范围内的信任时间或者禁用信任代理。
信任代理是继承自基类TrustAgentService(不在public SDK里面,带有@SystemApi的注解)的Android Service。基类TrustAgentService提供了诸如使能信任代理(setManagingTrust()),授予/取消信任(grant/revokeTrust()),还有一系列的回调方法,如下所示:
/frameworks/base/core/java/android/service/trust/TrustAgentService.java
public class TrustAgentService extends Service {
public void onUnlockAttempt(boolean successful) {
}
public void onTrustTimeout() {
}
private void onError(String msg) {
Slog.v(TAG, "Remote exception while " + msg);
}
public boolean onSetTrustAgentFeaturesEnabled(Bundle options) {
return false;
}
public final void grantTrust(
final CharSequence message,
final long durationMs, final boolean initiatedByUser) {
//...
}
public final void revokeTrust() {
//...
}
public final void setManagingTrust(boolean managingTrust) {
//...
}
@Override
public final IBinder onBind(Intent intent) {
return new TrustAgentServiceWrapper();
}
//...
}
如果要被系统识别到,一个信任代理需要在AndroidManifest.xml里面声明:1.action为android.service.trust.TrustAgentService的intent filter;2.BIND_TRUST_AGENT的权限。如下所示。这是为了保证可以绑定到该信任代理。在系统的AndroidManifest(frameworks/base/core/res/AndroidManifest.xml)可以看到,BIND_TRUST_AGENT权限的android:protectionLevel属性为"signature",意味着访问信任代理需要系统签名。允许跨进程调用到信任代理的Binder api也由TrustAgentService基类提供。
...
系统设置会扫描匹配上述intent filter的应用,检测它们是否需要用< uses-permission>指定了PROVIDE_TRUST_AGENT权限(定义在名为"android"的包中:frameworks/base/core/res/AndroidManifest.xml),如果所有条件满足就将其显示在信任代理列表界面(Settings->Security->Trust agents)中。此外,如果manifest文件里面包含的< meta-data>标签指向一个定义了setting activity的XML资源(如下所示),一个打开对应的setting activity的菜单入口会被添加到信任代理界面中。
信任代理默认不被激活(除非是system.img要求是激活的),但会在用户打开上图中的开关时激活。激活的信任代理由TrustManagerService掌管。TrustManagerService保存了很多和信任相关的事件。你可以使用以下命令dump出信任事件,获得当前的信任状态。
$ adb shell dumpsys trust
Trust manager state:
User "Owner" (id=0, flags=0x13) (current): trusted=0, trustManaged=1
Enabled agents:
org.nick.ghettounlock/.GhettoTrustAgent
bound=1, connected=1, managingTrust=1, trusted=0
Events:
#0 12-24 10:42:01.915 TrustTimeout: agent=GhettoTrustAgent
#1 12-24 10:42:01.915 TrustTimeout: agent=GhettoTrustAgent
#2 12-24 10:42:01.915 TrustTimeout: agent=GhettoTrustAgent
...
一旦信任代理被激活,授予信任会被特定的可观测的环境事件或者用户认证触发。一个经常用到的但是不是特别安全的例子:连接到"Home" wifi时触发解锁。这个功能可以通过响应android.net.wifi.STATE_CHANGE的广播来实现(见sample app。一旦侦测到连接到一个信任的SSID,广播接收器只需调用grantTrust()来进行授权。这可以通过多种方法来实现,如果信任代理和广播接收器在同一个包里面,一种比较直接的方式是使用LocalBroadcastManager(support库包含),如下所示。
static void sendGrantTrust(Context context,
String message,
long durationMs,
boolean initiatedByUser) {
Intent intent = new Intent(ACTION_GRANT_TRUST);
intent.putExtra(EXTRA_MESSAGE, message);
intent.putExtra(EXTRA_DURATION, durationMs);
intent.putExtra(EXTRA_INITIATED_BY_USER, initiatedByUser);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
// in the receiver
@Override
public void onReceive(Context context, Intent intent) {
if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(intent.getAction())) {
WifiInfo wifiInfo = (WifiInfo) intent
.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
// ...
if (secureSsid.equals(wifiInfo.getSSID())) {
GhettoTrustAgent.sendGrantTrust(context, "GhettoTrustAgent::WiFi",
TRUST_DURATION_5MINS, false);
}
}
}
grantTrust()会调用锁屏组件安装的ITrustAgentServiceCallback 回调并设置一个flag。如果信任时间已过,用户必须重新输入图案或PIN码或密码来解锁屏幕。当前的解锁状态会以一个挂锁图标的形式呈现在锁屏界面底部:当解锁时,表示当前的环境是可信任的。即使当前环境是可信任的,用户也可以点击挂锁图标来对设备进行重新加锁。
"Smart Lock"是GoogleTrustAgent组件的用户称呼,包含在GMS包中,见下面dump出来的信息:
$ adb shell dumpsys trust
Trust manager state:
User "Owner" (id=0, flags=0x13) (current): trusted=1, trustManaged=1
Enabled agents:
com.google.android.gms/.auth.trustagent.GoogleTrustAgent
bound=1, connected=1, managingTrust=1, trusted=1
message=""
信任代理提供了几种解锁触发方式:可信设备,可信地点和可信面孔。可信面孔是以往版本的脸部解锁的重新包装。它使用了相同的图像识别技术,但是更实用,因为当可信面孔启用时,锁屏会持续扫描以得到一个匹配的面孔而不是要求用户在拍照和处理图像时保持静止。就像可信面孔启动的界面提示一样,这种方式提供的安全等级是相当低的。可信地点是基于地理围篱科技实现的,这种技术在Google Play服务中已经实现有一段时间。可信地点使用与用户的Google账号相关的"Home"和“Work"地点来使得启动更加便捷,并且允许注册自定义的基于当前位置的地点或者Google Maps中任意可选的坐标。就像帮助窗口提到的,可信地点的精度无法保证,信任的地点范围会达到100米。实际上,设备在超出范围一段时间之内仍能保持解锁状态。
可信设备目前包括两种设备:蓝牙和NFC。蓝牙选项允许设备在配对设备在一定范围内时保持解锁状态。这种特性依靠蓝牙内置的安全特性来实现,蓝牙选项的安全性依靠其匹配的设备。更新的设备例如Android Wear和Pebble watch,支持Secure Simple Pairing配对方式(4级安全等级),使用ECDH算法来产生一个共享链接密钥。在配对过程中,这些设备会显示一个基于双方设备的公钥的哈希值生成的6位数来提供设备认证功能,避免MITM攻击(这个特性被称为数字比较)。然而,老旧设备例如Meta Watch,蓝牙耳机和其他一些设备也支持可信设备功能。这些前代产品仅仅支持标准匹配,这些设备通过设备物理地址和4位数PIN码来产生认证密钥,然而这个PIN码通常是固定的,且被设置成一些简单易记的数值,例如’0000‘和’1234‘。这些设备容易被冒充。
Google的Smart Lock的实现里要求与信任设备保持持续的连接,一旦连接断掉,信任就会被撤销(更新:在Android5.1以前,一个信任的的连接能够不使用密钥来建立)。然而,就和引导界面所提到的,蓝牙范围是可变的而且能达到100米。因此,当连接到可信腕表设备时让设备保持解锁的使用场景看起来很靠谱,实际上,当可信的蓝牙设备在另一个房间时,Android设备仍能保持解锁状态。
Android 5.0(Lollipop)基于信任代理引入了全新的trust framework,用来告知系统何时设备处于可信任的环境中。因为锁屏监听信任事件,所以会根据当前用户的信任事件来改变其行为。配置信任代理的方式使得增强或代替传统的图案/PIN码/密码的用户认证方式变得更加容易。信任代理功能目前仅仅对系统应用开放。Google Play服务在Smart Lock提供了几种信任触发条件。虽然Smart Lock大大提高了设备易用性,但是目前Smart Lock的判断方法没有一种是精确或者安全的,所以要小心使用。