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
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
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();
}
//...
}
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>
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" />
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
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);
}
}
}
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
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);
}
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.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. Smart Lock
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=""
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.
0 comments:
Post a Comment