Unlocking Android devices using an OTP via NFC

Our last post showed how to use a contactless smart card to sign email on Android. While storing cryptographic keys used with PKI or PGP is one of the main use cases for smart cards, other usages are gaining popularity as well. Additionally, the traditional 'card' format has evolved and there are different devices that embed a secure element (basically, the smart card chip), and make its functionality available without requiring a bulky card reader. One popular and affordable device that embeds a secure element is the YubKey Neo from Yubico. In this post we'll show how you can use the YubiKey Neo to unlock your Android device over NFC.

One-time passwords

Before we discuss how the YubiKey NEO can be used to unlock an Android device, let's say a few words about OTPs. As the name implies, one-time passwords are passwords that are valid for a single login or transaction. OTPs can be generated based on an algorithm that derives each next password from the previous one, or by using some sort of challenge-response mechanism. Another approach is to use a shared secret, called a seed, along with some dynamic value such as a counter or a value derived from the current time. While OTP generation based on a shared seed is usually fairly easy to implement, the dynamic values at the OTP token (called a prover) and the verifier (authentication server) can get out of sync and validation algorithms need to account for that. 

Many OTP schemes are proprietary and incompatible with each other. Fortunately, widely adopted open standards exist as well, most notably the HMAC-based One Time Password (HOTP) algorithm developed by the Initiative for Open Authentication (OATH). HOTP uses a secret key and a counter as input to the HMAC-SHA1 message authentication code (MAC) algorithm, truncates the calculated MAC value and converts it to a to human readable code, usually a 6-digit number. A later variation is the TOTP (Time-Based One-Time Password) algorithm, which substitutes the counter for a value derived from the current Unix time (i.e., the number of seconds since midnight of January 1, 1970 UTC). The derived value T, is calculated using an initial time T0 and a step X as follows: T = (Current Unix time - T0) / X. Each generated OTP is valid for X seconds, by default 30. TOTP is used by Google Authenticator and the Yubico OATH applet which we will use in our demo.

YubiKey Neo

The original YubiKey (now called YubiKey Standard), was an innovative token for two-factor authentication (2FA). It has a USB interface and presents itself as a USB keyboard when pulgged in, and thus does not require any special drivers to use. It has a single capacitive button that outputs an OTP when pressed. Because the device functions as keyboard, the OTP can be automatically entered in any text field of a desktop or Web application, or even terminal window, requiring very little modification to exiting applications. The OTP is generated using a 128-bit key stored inside the device, either using Yubico's OTP algorithm, or the HOTP algorithm.

The YubiKey Neo retains the form factor of the original YubiKey, but adds an important new component: a secure element (SE), accessible both via USB and over NFC. The SE offers a JavaCard 3.0/JCOP 2.4.2-compatible execution environment, an ISO14443A NFC interface, Mifare Classic emulation and an NDEF applet for interaction with Yubikey functionality. When plugged into a USB port, depending on its configuration, the Neo presents itself either as a keybord (HID device), a standard CCID smart card reader, or both when in composite mode. As the SE is fully compatible with JavaCard and GlobalPlatform standards, additional applets can be loaded with standard tools. Recent batches ship with pre-installed  OATH, PGP and PIV applets, and the code for both the OATH and PGP applets is available. Yubico provides a Google Authenticator compatible Android application, Yubico Authenticator that allows you to store the keys used to generate OTPs on the Neo. This ensures that neither attackers who have physical access to your Android device, nor applications with root access can extract your OTP keys. 

The Android lockscreen

Before we can figure out how to unlock an Android device using an OTP we need to understand how the lockscreen works. The lockscreen is formally known as the keyguard and is implemented much like regular Android applications: with widgets laid out on a window. What makes it special is that its window lives on a very high window layer that other applications cannot draw on top of or get control over. Additionally, the keyguard intercepts the normal navigation buttons, making it impossible to bypass and thus 'locking' the device. The keyguard window layer is not the highest layer however: dialogs originating from the keyguard itself, and the status bar, can be drawn over the keyguard. You can see a list of the currently shown windows using the Hierarchy Viewer tool available with the ADT. When the screen is locked the active windows is the Keyguard window, as shown in the screenshot below.

Before Android 4.0, it was possible for third-party applications to show windows in the keyguard layer, and this approach was often used in order to intercept the Home button and implement 'kiosk' style applications. Since Android 4.0 however, adding windows to the keyguard layer requires the INTERNAL_SYSTEM_WINDOW signature permission, which is available only to system applications.

For a long time the keyguard was an implementation detail of Android's window system and was not separated into a dedicated component. With the introduction of lockscreen widgets, dreams (i.e., screensavers) and support for multiple users, the keyguard gained quite a lot of functionality and was eventually extracted in a dedicated system application, Keyguard, in Android 4.4. The Keyguard app lives in the com.android.systemui process, along with the core Android UI implementation. Most importantly for our purposes, the Keyguard app includes a service with a remote interface, IKeyguardService. This service allows its clients to check the current state of the keyguard, set the current user, launch the camera and hide or disable the keyguard. As can be expected, operations that change the state of the keyguard are protected by a system signature permission, CONTROL_KEYGUARD.

Unlocking the keyguard

Stock Android provides three main methods to unlock the keyguard: by drawing a pattern, by entering a PIN or password, or by using image recognition, aka Face Unlock, also referred to as 'weak biometric'. The pattern, PIN and passphrase methods are essentially equivalent: they compare the hash of the user input to a hash stored on the device and unlock it if the values match. The hash for the pattern lock is stored in /data/system/gesture.key as an unsalted SHA-1 value. The hash of the PIN/password is a combination of the SHA-1 and MD5 hash values of  the user input, salted with a random value. It is stored in the /data/misc/password.key file. The Face Unlock implementation is proprietary and no details are available about the format of the stored data. Normally not visible to the user are the Google account password unlock method (used when the device is locked after too many incorrect unlock attempts) and the unlock method that uses the PIN or PUK of the SIM card. The Google unlock method uses the proprietary Google Login Service to verify the entered password, and the PIN/PUK method simply sends commands to the SIM card via the RIL interface.

As you can see, all unlock methods are based on a fixed PIN, password or pattern. Except in the case of a long and complex password, which is rather hard to input on a touchscreen keyboard, all unlock secrets usually have low entropy and can easily be guessed or bruteforced. Android partially protects against such attacks by permanently locking the device after too many unsuccessful attempts. Additionally security polices introduced by a device administrator application can enforce PIN/password complexity rules and even wipe the device after too many unsuccessful attempts.

One approach to improve the security of the keyguard is to use an OTP in order to unlock the device. While this is not directly supported by Android, it can be implemented on production devices by using a device administrator application that periodically changes the unlock PIN or password using the DevicePolicyManager API. One such application is TimePIN (which this post was in part inspired by) which sets the unlock password based on the current time. TimePIN allows you to set different modifiers that are applied when calculating the current PIN. Modifiers can be stacked, so the transformation can become complex, but still easy to remember. A secret component, called an offset can be mixed in for added security.

Unlocking via NFC

Authentication methods are usually based on something you know, something only you have, or a combination of the two (two-factor authentication, 2FA). The pattern and PIN/password unlock methods are based on something you know, and Face Unlock can be thought of as based on something you have (your face or a really good picture). However, Face Unlock allows for a fallback to PIN or password when it cannot detect a face, so it can still be unlocked by something you know.

An alternative way to use something you have to unlock the device is to use an NFC tag. This is not supported by stock Android, but is implemented in some devices, for example the Motorola X (marketed as Motorola Skip). While the Motorola Skip is a proprietary solution and no implementation details are available, apps that offer similar functionality such as NFC LockScreenOff Enabler compare the UID of the read tag to a list of stored values and unlock the device if the UID is in the list. While this is fairly secure as the UID of most NFC tags is read-only, cards that allow for UID modification are available, and a programmable NFC card emulator can emit any UID.

One problem with implementing NFC unlock is that by default Android does not scan for NFC devices when the screen is turned off or locked. This is intended as a security measure, because if the device reads NFC tags while the screen is off, vulnerabilities can be triggered without physical access to the device or the owner noticing, as has been demonstrated. NFC LockScreenOff Enabler and similar applications can get around this limitation when running on rooted devices by installing hooks into system methods, thus allowing the NFC system service configuration to be modified at runtime.

Unlocking using the YubiKey Neo

As we mentioned in the 'YubiKey Neo' section, Yubico provides both a JavaCard applet and a companion Android app that together implement TOTP compatible with Google Authenticator. The Yubico Authenticator app is initialized just like its Google counterpart -- either manually or by scanning a QR code. The difference is that the Yubico Authenticator saves the OTP seed on the device only temporarily, and once it's written to the Neo, deletes it. To display the current OTP, you need to touch the Neo while the app is active, and touch it again after the OTPs expire. If you don't want to enter keys and accounts manually you can use a QR code generator such as the one provided by the ZXing project to generate a URI that includes an account name and seed. The URI format is available on the Google Authenticator Wiki.

While unlocking the keyguard certainly doesn't need the full functionality of the Google Authenticator app, displaying the current OTP is useful for debugging and initializing with a QR code is quite convenient. That's why for our demo we will simply modify the Authenticator app slightly, instead of writing another OTP source. As we need to provide the OTP to the system NFC service, which runs in a different process, we add a remote AIDL service with a single method that returns the current OTP:

interface IRemoteOtpSource {

     String getNextCode(String accountName);

}

The NFC service can then bind to the OTP service that implements this interface and retrieve the current OTP. Of course, providing the OTP to everyone is not a great idea, so we protect the service with a signature permission that can only be granted to system apps by signing our  RemoteAuthenticator app with the platform certificate:

<manifest ...>
...
    <permission 
        android:name="com.google.android.apps.remoteauthenticator.GET_OTP_CODE" 
        android:protectionlevel="signature"/>
...
   <application ...>
...
     <service android:enabled="true" android:exported="true" 
         android:name="com.google.android.apps.authenticator.OtpService" 
         android:permission="com.google.android.apps.remoteauthenticator.GET_OTP_CODE">
        </service>
    </application>

</manifest>

The full source code of the RemoteAuthenticator app is available on Github. Once installed, the app needs to be initialized with the same key and account name as the OATH applet on the YubiKey Neo. Our sample NFC unlock implementations looks for an account named 'lockscreen' when it detects the OATH applet. The interface of the modified app is identical to that of Google Authenticator:



Before we can use an NFC tag to unlock the keyguard, we need to make sure the system NFC service can detect NFC tags even when the keyguard is locked. As we mentioned earlier, that is not the case in stock Android, so we change the default polling mode from SCREEN_STATE_ON_UNLOCKED to SCREEN_STATE_ON_LOCKED in NfcService.java:

package com.android.nfc;
...

public class NfcService implements DeviceHostListener {
...
    /** minimum screen state that enables NFC polling (discovery) */
    static final int POLLING_MODE = SCREEN_STATE_ON_LOCKED;
...

}

With this done, we can hook into the NFC service tag dispatch sequence, and, borrowing some code from the Yubico Authenticator app, check whether the scanned tag includes an OATH applet. If so, we read out the current OTP and compare it with the OTP returned by the RemoteAuthenticator app installed on the device. If the OTPs match, we dismiss the keyguard and let the dispatch continue. If the tag doesn't contain an OTP applet, or the OTPs don't match, we do not dispatch the tag. To unlock the keyguard we simply call the keyguardDone() method of the system KeyguardService. The unlock process might look something like this:


Full source code for the modified NFC service is available on Github (in the 'otp-unlock' branch). Note that while this demo implementation handles basic error cases like OATH applet not found or connection with tag lost, it is not particularly robust. It only tries to connect to remote services once, and if  either of them is unavailable, NFC unlock is disabled altogether. It doesn't provide any visual indication that NFC unlock is happening either, the keyguard simply disappears as seen in the video above. Another missing piece is multi-user support: in order to support multiple users, the code needs to look for the current users's account on the NFC device, and not for a hardcoded name. Finally, the NFC unlock as currently implemented is not a full unlock method: it cannot be selected in the Screen security settings, but simply supplements the currently selected unlock method.

Summary

As of Android 4.4, the Android keyguard can be queried by third party applications and dismissed by apps that hold the CONTROL_KEYGUARD permission. This makes it easy to implement alternative unlock mechanisms, such as NFC unlock. However, NFC tag polling is disabled by default when the screen is locked, so adding an NFC unlock mechanism requires modifying the system NFC service. For added security, NFC unlock methods should rely not only on the UID of the scanned tag, but on some secret information that is securely stored inside the tag. This could be a private key for use in some sort of signature-based authentication scheme, or an OTP seed. An easy way to implement OTP-based NFC unlock is to use the Yubico OATH applet, pre-installed on the YubiKey Neo, along with a modified Google Authenticator app that offers a remote interface to read the current OTP. 

Comments

Unknown said…
Hi, really good article. I check at your sourcecode and I see you didn't use the CONTROL_KEYGUARD permission. Also, I'm unable to compile it correctly, cause I can't find/import com.android.internal.policy.xxx (like IKeyguardService for example).

One question, is it possible for third party application (which are distributed through google store) to use the unlock mechanism you describe? like invoking the keyguardDone() method? Or is it just for platform signed apk?

Thanks,
Max

Popular posts from this blog

Decrypting Android M adopted storage

Unpacking Android backups

Using app encryption in Jelly Bean