HeadSpin Documentation
Documentation

Testing biometric authentication in Android

Biometric Testing in Android

Until recently, automating tests on a mobile app that requires fingerprint or face authentication has been a challenge. HeadSpin provides a solution by offering a biometric instrumentation toolset which comprises of a library, API endpoints, and web UI to help automate biometric testing.

Prerequisites

Before using the biometric SDK, please review these important points:

  • The device running the test app should have at least one real valid fingerprint or face ID registered.
  • The application using this SDK should never be distributed outside the use of HeadSpin's secured devices.

Getting started

Android has several different biometric SDKs:

There is a corresponding HS biometric SDK for each of those which wraps the Android SDK to allow remotely controlling biometric authentication. Download the AAR library containing the biometric SDK version you want to use from the download section. Follow these steps in Android Studio to include the library in your project:

  1. Click File > New > New Module.
  2. Click Import .JAR/.AAR Package then click Next.
  3. Select the location of the AAR to use, for example HSBiometricSDK-androidx/HSBiometricSDK-androidx-release.aar then click Finish.
  4. Add a corresponding line to your build.gradle, for example:

<code class="dcode">gradle implementation project(":HSBiometricSDK-androidx-release")</code>

Please see Add your library as a dependency in the Android documentation for additional information.

Using the HeadSpin Biometric SDK for Android

Each SDK will talk to HeadSpin's biometric API over local http which requires the following setting in AndroidManifest.xml:


<application
    android:usesCleartextTraffic="true"

To use each SDK wrap the corresponding Android class in the following way:

HSBiometricSDK-androidx

Package io.headspin.io.biometrics.androidx wraps androidx.biometric.


public class HSBiometricPrompt {
  void authenticate(BiometricPrompt.PromptInfo, BiometricPrompt.CryptoObject)
}

HSBiometricSDK-biometrics

Package io.headspin.io.biometrics.biometrics wraps android.hardware.biometrics.


public class HSBiometricPrompt {
  class AuthenticationCallback : BiometricPrompt.AuthenticationCallback() {
      void onAuthenticationRemote(BiometricPrompt.CryptoObject?)
  }
  HSBiometricPrompt(BiometricPrompt)
  void authenticate(BiometricPrompt.CryptoObject, CancellationSignal, Executor, HSBiometricPrompt.AuthenticationCallback)
}

HSBiometricSDK-fingerprint

Package io.headspin.io.biometrics.fingerprint wraps android.hardware.fingerprint.


public class HSFingerprintManager extends FingerprintManager {
  static HSFingerprintManager getSystemService(Context)
  void authenticate(FingerprintManager.CryptoObject,
    CancellationSignal,
    Int,
    FingerprintManager.AuthenticationCallback,
    Handler)
}

UI integration

When the SDK is used the HeadSpin UI will display a dialog which can be used to trigger biometric authentication in the app.

biometric authentication

Sample Apps

The sample apps come in two versions - one not using the HeadSpin SDK and then a test version with the required changes to use the SDK.

One way to keep a separate test version of an Android app is to use product flavors with separate variants. The samples are configured like below in gradle, adding two variants called "realBiometrics" and "headspinRemote".


flavorDimensions 'biometrics'
productFlavors {
    realBiometrics {
    }
    headspinRemote {
        applicationIdSuffix = '.hs'
    }
}

Different source files can now be placed into "realBiometrics" and "headspinRemote" folders - we moved biometrics related code into a class under realBiometrics and then provide a separate implementation with the HS SDKs in the headspinRemote folder.

Simple Login Sample App

This is a very basic example which uses an Android biometric SDK to ask for authentication and then changes a button depending on the result.

androidx.biometric

The only change required is to use HSBiometricPrompt instead of BiometricPrompt.


// realBiometrics
return BiometricPrompt(context, ContextCompat.getMainExecutor(context), callback)

// headspinRemote
import io.headspin.biometrics.androidx.HSBiometricPrompt
return HSBiometricPrompt(context, ContextCompat.getMainExecutor(context), callback)

androidx.biometric (Java)

The SDKs can also be used from Java instead of Kotlin.


// realBiometrics
BiometricPrompt prompt = new BiometricPrompt(context, ContextCompat.getMainExecutor(context), callback);

// headspinRemote
import io.headspin.biometrics.androidx.HSBiometricPrompt;
HSBiometricPrompt prompt = new HSBiometricPrompt(context, ContextCompat.getMainExecutor(context), callback);

android.hardware.fingerprint

The only change to use the HS biometric SDK is to replace FingerprintManager with HSFingerprintManager


// realBiometrics
private var manager: FingerprintManager = getSystemService(context, FingerprintManager::class.java)

// headspinRemote
private var manager: HSFingerprintManager = HSFingerprintManager.getSystemService(context)

android.hardware.biometrics

This API requires two changes to use the HeadSpin Biometric SDK. First wrap the BiometricPrompt object into a HSBiometricPrompt object.

And then, since the <code class="dcode">authenticate</code> method of the wrapped class uses a different callback class, we are using a custom callback derived from BiometricPrompt.AuthenticationCallback which implements the missing method.


// realBiometrics
open class AuthenticationCallback : BiometricPrompt.AuthenticationCallback() {
    open fun onAuthenticationRemote(result: BiometricPrompt.CryptoObject?) { }
}

wrapped.authenticate(crypto, cancellationSignal, executor, authenticationCallback)

// headspinRemote
import io.headspin.io.biometrics.biometrics.HSBiometricPrompt

open class AuthenticationCallback : HSBiometricPrompt.AuthenticationCallback() {

}

val bio = HSBiometricPrompt(wrapped)
bio.authenticate(crypto, cancellationSignal, executor, authenticationCallback)

Encryption Sample App

The HeadSpin biometric SDK does not replace any of the underlying Android functionality - it just will simulate biometric authentication by calling a success callback in response to a remote command (and close the real biometric prompt). While this works well for something simple and insecure like the previous example it will not allow testing of features using actual biometric security without bypassing more checks.

The encryption sample uses a secure biometric key to encrypt and decrypt a string. To make it work the test version of the app has to replace the secure key with an insecure key, which can be toggled at runtime to demonstrate the difference. The insecure key can of course be used without any biometric authentication at all which is demonstrated with a second runtime toggle.

androidx.biometric

Uses HSBiometricPrompt in the test version where BiometricPrompt is used in the actual version of the app (just like in the login sample). However in the test version it also uses a non-biometric key instead of a biometric key as the key-store cannot be accessed with the fake authentication otherwise:


if (requiresAuthentication) {
    builder.setUserAuthenticationRequired(true)
    builder.setInvalidatedByBiometricEnrollment(true)
}

android.hardware.fingerprint

With Android's FingerprintManager SDK HSFingerprintManager.getSystemService is used instead of getSystemService to obtain a HSFingerprintManager when compiling the test version. And when creating the key setUserAuthenticationRequired and setInvalidatedByBiometricEnrollment is not used in this example in order to allow remote use of the key.


// realBiometrics
manager = getSystemService(context, FingerprintManager::class.java)

// headspinRemote
manager = HSFingerprintManager.getSystemService(context)

if (requiresAuthentication) {
    builder.setUserAuthenticationRequired(true)
    builder.setInvalidatedByBiometricEnrollment(true)
}

android.hardware.biometrics

In addition to using HSBiometricPrompt instead of BiometricPrompt (just like in the simple login example) this requires the same key generation changes as the FingerprintManager example. In short, for testing we remove the biometric requirement from the key, but keep the (fake) biometric prompt to make it behave similar to the real application.