Dissecting Lollipop's Smart Lock

Android 5.0 (Lollipop) has been out for a while now, and most of its new features have been introduced, benchmarked, or complained about extensively. The new release also includes a number of of security enhancements, of which disk encryption has gotten probably the most media attention. Smart Lock (originally announced at Google I/O 2014), which allows bypassing the device lockscreen when certain environmental conditions are met, is probably the most user-visible new security feature. As such, it has also been discussed and blogged about extensively. However, because Smart Lock is a proprietary feature incorporated in Google Play Services, not many details about its implementation or security level are available. This post will look into the Android framework extensions that Smart Lock is build upon, show how to use them to create your own unlock method, and finally briefly discuss its Play Services implementation.

Trust agents

Smart Lock is build upon a new Lollipop feature called trust agents. To quote from the framework documentation, a trust agent is a 'service that notifies the system about whether it believes the environment of the device to be trusted.'  The exact meaning of 'trusted' is up to the trust agent to define. When a trust agent believes it can trust the current environment, it notifies the system via a callback, and the system decides how to relax the security configuration of the device.  In the current Android incarnation, being in a trusted environment grants the user the ability to bypass the lockscreen.

Trust is granted per user, so each user's trust agents can be configured differently. Additionally, trust can be granted for a certain period of time, and the system automatically reverts to an untrusted state when that period expires. Device administrators can set the maximum trust period trust agents are allowed to set, or disable trust agents altogether. 

Trust agent API

Trust agents are Android services which extend the TrustAgentService base class (not available in the public SDK). The base class provides methods for enabling the trust agent (setManagingTrust()), granting and revoking trust (grant/revokeTrust()), as well as a number of callback methods, as shown below.

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();
}


//...
}

To be picked up by the system, a trust agent needs to be declared in AndroidManifest.xml with an intent filter for the android.service.trust.TrustAgentService action and require the BIND_TRUST_AGENT permission, as shown below. This ensures that only the system can bind to the trust agent, as the BIND_TRUST_AGENT permission requires the platform signature. A Binder API, which allows calling the agent from other processes, is provided by the TrustAgentService base class. 

<manifest ... >

<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
<uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />

<application ...>
<service android:exported="true"
android:label="@string/app_name"
android:name=".GhettoTrustAgent"
android:permission="android.permission.BIND_TRUST_AGENT">
<intent-filter>
<action android:name="android.service.trust.TrustAgentService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

<meta-data android:name="android.service.trust.trustagent"
android:resource="@xml/ghetto_trust_agent"/>
</service>
...
</application>
</manifest>

The system Settings app scans app packages that match the intent filter shown above, checks if they hold the PROVIDE_TRUST_AGENT signature permission (defined in the android package) and shows them in the Trust agents screen (Settings->Security->Trust agents) if all required conditions are met. Currently only a single trust agent is supported, so only the first matched package is shown. Additionally, if the manifest declaration contains a <meta-data> tag that points to an XML resource that defines a settings activity (see below for an example), a menu entry that opens the settings activity is injected into the Security settings screen.

<trust-agent xmlns:android="http://schemas.android.com/apk/res/android"
android:title="Ghetto Unlock"
android:summary="A bunch of unlock triggers"
android:settingsActivity=".GhettoTrustAgentSettings" />


Here's how the Trusted agents screen might look like when a system app that declares a trusted agent is installed.

Trust agents are inactive by default (unless part of the system image), and are activated when the user toggles the switch in the screen above. Active agents are ultimately managed by the system TrustManagerService which also keeps a log of trust-related events. You can get the current trust state and dump the even log using the dumpsys command as shown below.

$ 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
...

Granting trust

Once a trust agent is installed, a trust grant can be triggered by any observable environment event, or directly by the user (for example, by via an authentication challenge). An often requested, but not particularly secure (unless using a WPA2 profile that authenticates WiFi access points), unlock trigger is connecting to a 'home' WiFi AP. This feature can be easily implemented using a broadcast receiver that reacts to android.net.wifi.STATE_CHANGE (see sample app; based on the sample in AOSP). Once a 'trusted' SSID is detected, the receiver only needs to call the grantTrust() method of the trust agent service. This can be achieved in a number of ways, but if both the service and the receiver are in the same package, a straightforward way is to use a LocalBroadcastManager (part of the support library) to send a local broadcast, as shown below.

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);
}
}
}


This will call the TrustAgentServiceCallback installed by the system lockscreen and effectively set a per-user trusted flag. If the flag is true, the lockscreen implementation allows the keyguard to be dismissed without authentication. Once the trust timeout expires, the user must enter their pattern, PIN or password in order to dismiss the keyguard. The current trust state is displayed at the bottom of the keyguard as a padlock icon: when unlocked, the current environment is trusted; when locked, explicit authentication is required. The user can also manually lock the device by pressing the padlock, even if an active trust agent currently has trust.

NFC unlock

As discussed in a previous post, implementing NFC unlock in previous Android versions was possible, but required some modifications to the system NFCService, because the NFC controller was not polled while the lockscreen is displayed. In order to make implementing NFC unlock possible, Lollipop introduces several hooks into the NFCService, which allow NFC polling on the lockscreen. If a matching tag is discovered, a reference to a live Tag object is passed to interested parties. Let's look into the how this is implementation in a bit more detail.

The NFCAdapter class has a couple of new (hidden) methods that allow adding and removing an NFC unlock handler (addNfcUnlockHandler() and removeNfcUnlockHandler(), respectively). An NFC unlock handler is an implementation of the NfcUnlockHandler interface shown below.

interface NfcUnlockHandler {
public boolean onUnlockAttempted(Tag tag);
}

When registering an unlock handler you must specify not only the NfcUnlockHandler object, but also a list of NFC technologies that should be polled for at the lockscreen. Calling the addNfcUnlockHandler() method requires the WRITE_SECURE_SETTINGS signature permission.

Multiple unlock handlers can be registered and are tried in turn until one of them returns true from onUnlockAttempted(). This terminates the NFC unlock sequence, but doesn't actually dismiss the keyguard. In order to unlock the device, an NFC unlock handler should work with a trust agent in order to grant trust. Judging from NFCService's commit log, this appears to be a fairly recent development: initially, the Settings app included functionality to register trusted tags, which would automatically unlock the device (based on the tag's UID), but this functionality was removed in favour of trust agents.

Unlock handlers can authenticate the scanned NFC tag in a variety of ways, depending on the tag's technology. For passive tags that contain fixed data, authentication typically relies either on the tag's unique ID, or on some shared secret written to the tag. For active tags that can execute code, it can be anything from an OTP to full-blown multi-step mutual authentication. However, because NFC communication is not very fast, and most tags have limited processing power, a simple protocol with few roundtrips is preferable. A simple implementation that requires the tag to sign a random value with its RSA private key, and then verifies the signature using the corresponding public key is included in the sample application. For signature verification to work, the trust agent needs to be initialized with the tag's public key, which in this case is imported via the trust agent's settings activity shown below.

Smart Lock

'Smart Lock' is just the marketing name for the GoogleTrustAgent which is included in Google Play Services (com.google.android.gms package), as can be seen from the dumpsys output below.

$ 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=""



This trust agent offers several trust triggers: trusted devices, trusted places and a trusted face. Trusted face is just a rebranding of the face unlock method found in previous versions. It uses the same proprietary image recognition technology, but is significantly more usable, because, when enabled, the keyguard continuously scans for a matching face instead of requiring you to stay still while it takes and process your picture. The security level provided also remains the same -- fairly low, as the trusted face setup screen warns. Trusted places is based on the geofencing technology, which has been available in Google Play services for a while. Trusted places use the 'Home' and 'Work' locations associated with your Google account to make setup easier, and also allows for registering a custom place based on the current location or any coordinates selectable via Google Maps. As a helpful popup warns, accuracy cannot be guaranteed, and the trusted place range can be up to 100 meters. In practice, the device can remain unlocked for a while even when this distance is exceeded.

Trusted devices supports two different types of devices at the time of this writing: Bluetooth and NFC. The Bluetooth option allows the Android device to remain unlocked while a paired Bluetooth device is in range. This features relies on Bluetooth's built-in security mechanism, and as such its security depends on the paired device. Newer devices, such as Android Wear watches or the Pebble watch, support Secure Simple Pairing (Security Mode 4), which uses Elliptic Curve Diffie-Hellman (ECDH) in order to generate a shared link key. During the paring process, these devices display a 6-digit number based on a hash of both devices' public keys in order to provide device authentication and protect against MiTM attacks (a feature called numeric comparison). However, older wearables (such as the Meta Watch), Bluetooth earphones, and others are also supported. These previous-generation devices only support Standard Pairing, which generates authentication keys based on the device's physical address and a 4-digit PIN, which is usually fixed and set to a well-know value such as '0000' or '1234'. Such devices can be easily impersonated.

Google's Smart Lock implementation requires a persistent connection to a trusted device, and trust is revoked once this connection is broken (Update: apparently a trusted connection can be established without a key on Android < 5.1 ). However, as the introductory screen (see below) warns, Bluetooth range is highly variable and may extend up to 100 meters. Thus while the 'keep device unlocked while connected to trusted watch on wrist' use case makes a lot of sense, in practice the Android device may remain unlocked even when the trusted Bluetooth device (wearable, etc.) is in another room.


As discussed earlier, an NFC trusted device can be quite flexible, and has the advantage that, unlike Bluetooth, proximity is well defined (typically not more than 10 centimeters). While Google's Smart Lock seems to support an active NFC device (internally referred to as the 'Precious tag'), no such device has been publicly announced yet. If the Precious is not found, Google's NFC-based trust agent falls back to UID-based authentication by saving the hash of the scanned tag's UID (tag registration screen shown below). For the popular NFC-A tags (most MIFARE variants) this UID is 4 or 7 bytes long (10-byte UIDs are also theoretically supported). While using the UID for authentication is a fairly wide-spread practice, it was originally intended for anti-collision alone, and not for authentication. 4-byte UIDs are not necessarily unique and may collide even on 'official' NXP tags. While the specification requires 7-byte IDs to be both unique (even across different manufacturers) and read-only, cards with a rewritable UID do exists, so cloning a MIFARE trusted tag is quite possible. Tags can also be emulated with a programmable device such as the Proxmark III. Therefore, the security level provided by UID-based authentication is not that high.

Summary

Android 5.0 (Lollipop) introduces a new trust framework based on trust agents, which can notify the system when the device is in a trusted environment. As the system lockscreen now listens for trust events, it can change its behaviour based on the trust state of the current user. This makes it easy to augment or replace the traditional pattern/PIN/password user authentication methods by installing  trust agents. Trust agent functionality is currently only available to system applications, and Lollipop can only support a single active trust agent. Google Play Services provides several trust triggers (trustlets) under the name 'Smart Lock' via its trust agent. While they can greatly improve device usability, none of the currently available Smart Lock methods are particularly precise or secure, so they should be used with care.

Comments