KeyguardManager / Security

User Identity – Part 3

Sometimes it is necessary to validate a user’s identity in order to provide access to sensitive information within an app. The traditional mechanism of a username and password has always been rather clunky on mobile because of small screens and soft keyboards. However with the arrival of hardware such as fingerprint scanners arriving on devices, there are some new much more user-friendly mechanisms that we can use for authentication. In this series we’ll explore how to implement these within our apps.

Previously we’ve looked at using the fingerprint scanner to determine the user’s identity, but what if we want to introduce some further security? The obvious answer is that we could introduce own own password scheme, but this poses the problem of how we should store the password in a secure manner, and also poses the UX / security dilemma of the user either having to remember yet another password, or using a common password (which becomes less secure).

However, there’s an alternative because Android has a built in security mechanism: the device lock screen which may require the user to use a pattern, pin or password to unlock the device. If we use this instead of our own implementation we save ourselves the issue of storing a password securely. But we also have a much improved UX because the user tends to unlock their device regularly, and so does not have to remember the technique.

The mechanism for triggering a lock screen authentication is actually pretty similar with the techniques we’ve already covered for triggering a fingerprint scan – we use a Cipher with a Key which will only become active once the lock screen authentication has been successfully completed. However, we can do even more than that. Whilst it’s easy enough to request a lock screen authentication each time the user enters a secure part of our app, we can also put a timeout on the authentication – meaning that if the user re-enters the secure area within the timeout period, then they do not have to re-authenticate.

So let’s take a look at the activity which does this:

public class ConfirmCredentialActivity extends BaseActivity {

    private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1;

    private KeyguardManager keyguardManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_credential);

        keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
    }

    @Override
    protected void onResume() {
        super.onResume();

        try {
            Cipher cipher = getTimedUserAuthCipher();
            cipher.doFinal(SECRET_BYTES);
        } catch (UserNotAuthenticatedException e) {
            showAuthenticationScreen();
            return;
        } catch (IllegalStateException e) {
            showError(R.string.no_lockscreen);
            return;
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        setContentView(R.layout.activity_user_identified);
    }

    private void showAuthenticationScreen() {
        Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null);
        if (intent != null) {
            startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
        }
    }
}

This is actually much simpler than the Activity for fingerprint scanner. All we do is get the Cipher and try to use it. If an exception is thrown then the type of exception can indicate the nature of the problem. In this case a UserNotAuthenticatedException means that the user has not been authenticated, and so we need to perform an authentication. IllegalStateException indicates that no lock screen was enabled when the key was created. These are the basic error states that we’re interested in.

If no exception is thrown then we simply display the layout containing the secure information.

The actual authentication is performed by creating an appropriate Intent from the KeyguardManager, and we start an Actvity from that Intent – this is where we pass the control over to the KeyguardManager to display the lock screen. At this point it is probably worth explaining the security reasoning behind using a Key / Cipher as this is probably where the security aspect is most obvious.

Switching between Activities is somewhat insecure. You can launch individual Activities using adb, and there are some third party apps which will enable you to do this as well. By marshalling the authentication state within the KeyStore (which only the system level components can update) we bypass any security issues inherent in the process of switching Activity because we have confidence that the correct system level component has made the necessary updates to the Key.

So, on to the key itself. The actual KeyTools code is largely unchanged – we just need to add a different KeyGenParameterSpec factory:

public final class KeyTools {

    private static final String PROVIDER_NAME = "AndroidKeyStore";
    private static final String USER_AUTH_KEY_NAME = "com.stylingandroid.fingerprint.USER_AUTH_KEY";
    private static final String TIMED_USER_AUTH_KEY_NAME = "com.stylingandroid.fingerprint.TIMED_USER_AUTH_KEY";
    .
    .
    .
    public static KeyTools newInstance() throws KeyToolsException {
        KeyStore keyStore;
        try {
            keyStore = KeyStore.getInstance(PROVIDER_NAME);
            keyStore.load(null);
        } catch (Exception e) {
            throw new KeyToolsException("Error initializing keystore: ", e);
        }
        Map<String, KeySpecGenerator> generators = new ArrayMap<>();
        generators.put(USER_AUTH_KEY_NAME, new UserAuthKeySpecGenerator(BLOCK_MODE, PADDING));
        generators.put(TIMED_USER_AUTH_KEY_NAME, new TimedUserAuthKeySpecGenerator(BLOCK_MODE, PADDING, TIMEOUT));
        return new KeyTools(keyStore, Collections.unmodifiableMap(generators));
    }
    .
    .
    .
    public Cipher getTimedUserAuthCipher() throws KeyToolsException, IllegalStateException, UserNotAuthenticatedException {
        return getCipher(TIMED_USER_AUTH_KEY_NAME);
    }
    .
    .
    .
}

The rest of the Key / Cipher generation is unchanged, all we need to add is the TimedUserAuthKeySpecGenerator implementation:

class TimedUserAuthKeySpecGenerator implements KeySpecGenerator {
    private final String blockMode;
    private final String padding;
    private final int timeout;

    TimedUserAuthKeySpecGenerator(String blockMode, String padding, int timeout) {
        this.blockMode = blockMode;
        this.padding = padding;
        this.timeout = timeout;
    }

    @Override
    public KeyGenParameterSpec generate(String keyName) {
        return new KeyGenParameterSpec.Builder(keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(blockMode)
                .setEncryptionPaddings(padding)
                .setUserAuthenticationRequired(true)
                .setUserAuthenticationValidityDurationSeconds(timeout)
                .build();
    }
}

This is almost identical to our UserAuthKeySpecGenerator implementation with only the addition of setUserAuthenticationValidityDurationSeconds() to force an expiry after the given period – in our sample app we’ve made this 30 seconds.

All that remains is to run this. Initially we require a lock screen authentication, but if we exit an kill the app, and re-start it within 30 seconds no authentication is required:

That concludes our look at identifying and authenticating users.

The source code for this article is available here.

© 2015, Mark Allison. All rights reserved.

Copyright © 2015 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.