YubiKit Android Module

This module contains the main implementation of the SDK for Android, and is required for all Android development.

Dependency

To add the Android module as a dependency to your project, add the following to your gradle configuration:

dependencies {
  implementation 'com.yubico.yubikit:android:(insert version here)'
}

This module depends on the core module, which will automatically be added as a transitive dependency to your project.

Logging

Note
YubiKit 2.3.0 deprecated core Logger class in favor of slf4j logging abstraction. Logger will be removed in YubiKit 3.0.0. Migration steps are described in Logging Migration

Logging is provided through the slf4j interface. Because YubiKit does not provide logger implementation, applications must provide an implementation to enable logging.

logback-android

logback-android is an open-source implementation of slf4j which can be simply added to an existing Android project to enable YubiKit logging. To do so:

  1. Add required dependencies:

dependencies {
    implementation 'com.github.tony19:logback-android:3.0.0'
}
  1. Add assets/logback.xml. This file configures the logger behaviour. A Logger which outputs everything to logcat may look as follows:

<configuration xmlns="https://tony19.github.io/logback-android/xml"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://tony19.github.io/logback-android/xml https://cdn.jsdelivr.net/gh/tony19/logback-android/logback.xsd" >

    <appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
        <encoder>
            <pattern>%msg</pattern>
        </encoder>
    </appender>

    <!-- Write TRACE (and higher-level) messages to logcat -->
    <root level="TRACE">
        <appender-ref ref="logcat" />
    </root>
</configuration>

Read more about logback configuration here: https://github.com/tony19/logback-android/wiki

Note
slf4j is licensed under MIT license and logback-android is licensed under Apache License 2.0

Log levels

Using slf4j adds more flexibility for apps to describe precisely which events should or should not be logged. Depending on the used implementation, one can redirect logging to different output streams based on various criteria such as log level, logger name and similar. For detailed information about logging through slf4j see SLF4J Homepage.

YubiKit for Android uses slf4j log levels for following events:

Level

Event

info

Successfully Completed events, such as Configuration written, Private key generated and similar.

debug

Detailed information about what is happening, useful for finding errors during development. No sensitive information is logged.

warn

Unexpected states which do not prevent execution continuation.

error

Unrecoverable errors in communication, which prevent YubiKit to continue it’s flows.

trace

Detailed information about data which are being being read from/written to devices. This log level may contain sensitive information and should not be enabled for production use.

The YubiKit Manager

Accessing a YubiKey is done with an instance of the YubiKitManager. It provides access over both USB and NFC, and allows discovery of new YubiKeys.

To create an instance of YubikitManager you will need an Android Context, such as an Activity. You can instantiate the class in your Activity’s onCreate() method.

YubiKitManager yubiKitManager = new YubiKitManager(context);

YubiKeys over USB

Note
To make debugging over USB easier, we recommend setting up ADB to connect using WiFi, as described here: https://developer.android.com/studio/command-line/adb#wireless

To connect to a YubiKey over USB, you need to call startUsbDiscovery with a callback to handle attached YubiKeys. This callback will be invoked whenever a new YubiKey is detected over USB. Again, the onCreate() method of your Activity is a good place for this.

yubiKitManager.startUsbDiscovery(new UsbConfiguration(), device -> {
  // A YubiKey was plugged in
  if(!device.hasPermission()) {
    // Using the default UsbConfiguration this will never happen, as permission will automatically
    // be requested by the YubiKitManager, and this method won't be invoked unless it is granted.
  }

  device.setOnClosed(() -> {
    // Do something when the YubiKey is removed
  }))
});

To stop listening for USB events, call the stopUsbDiscovery() method. This can be done in your Activity’s onDestroy() method:

yubiKitManager.stopUsbDiscovery();

YubiKeys over NFC

To connect to a YubiKey over NFC, you need to call startNfcDiscovery with a callback to handle NFC YubiKey events. This callback will be invoked whenever a new YubiKey is detected over NFC. NFC requires an Activity in the foreground, and you should stop listening for NFC events when the Activity goes into the background. You can use the Activity’s onResume() method to start listening, and its onPause() method to stop:

@Override
public void onResume() {
  super.onResume();
  try {
    yubiKitManager.startNfcDiscovery(new NfcConfiguration(), this, device -> {
      // A YubiKey was brought within NFC range
    });
  } catch (NfcNotAvailableException e) {
    if (e.isDisabled()) {
      // show a message that user needs to turn on NFC for this feature
    } else {
      // NFC is not available so this feature does not work on this device
    }
  }
}

@Override
public void onPause() {
  yubiKitManager.stopNfcDiscovery(this);
  super.onPause();
}
NFC timeouts

NFC connection times out after timeout value of NfcConfiguration() object. Some operations which run on the YubiKey, for example generation of RSA keys, can take long time to complete. To avoid timeouts, start NFC discovery with configuration with reasonable timeout value.

    ...
    yubiKitManager.startNfcDiscovery(
        new NfcConfiguration().setTimeout(25000), // 25 seconds
    ...

Opening a connection

Regardless of if you are using USB or NFC, you need to open a connection to the YubiKey to do anything with it. A YubiKeyDevice manages its own worker thread in which all communication with the Connection should be done. Interaction with a Connection is done within a Callback, and the Connection is automatically closed once the Callback completes.

There are different types of connections, which can be used for different applications. Here is an example of opening a SmartCardConnection and doing some low-level communication with it. In practice you will more likely use one of the Session classes defined in the other modules.

// Request a new SmartCardConnection:
device.requestConnection(SmartCardConnection.class, result -> {
  // The result is a Result<SmartCardConnection, IOException>, which represents either a successful connection, or an error.
  try {
    SmartCardConnection connection = result.getValue();  // This may throw an IOException
    // The SmartCardProtocol offers a the ability of sending APDU-based smartcard commands
    SmartCardProtocol protocol = new SmartCardProtocol(connection);
    byte[] aid = new byte[] {0xA0, 0x00, 0x00, 0x03, 0x08};
    protocol.select(aid);  // Select a smartcard application (this may throw an ApplicationNotAvailableException)
    protocol.sendAndReceive(new Apdu(0x00, 0xA4, 0x00, 0x00)));
  } catch(ApplicationNotAvailableException | IOException e) {
    // Handle errors
  }
});

For more control of the connection instance, YubiKit 2.3.0 adds YubiKeyConnection YubiKeyDevice.openConnection(type) API. The caller of this method is responsible for closing the connection and for making sure that the object is accessed in a thread-safe way. Because YubiKeyConnection derives from Closable, it is possible to use try-with-resources Java statement to automatically close the connection. Example:

try {
    try (SmartCardConnection connection = device.openConnection(SmartCardConnection.class)) {
        SmartCardProtocol protocol = new SmartCardProtocol(connection);
        byte[] aid = new byte[]{(byte) 0xA0, 0x00, 0x00, 0x03, 0x08};
        protocol.select(aid);
        protocol.sendAndReceive(new Apdu(0x00, 0xA4, 0x00, 0x00, null));
    }
} catch(ApplicationNotAvailableException | IOException e) {
    // Handle errors
}

UI elements

The module provides some re-usable components which can be useful for implementing common functionality.

The YubiKey Prompt Activity

An Android Activity dialog which prompts the user to connect their YubiKey, and performs some action with it. To use, implement a YubiKeyPromptAction, and specify it in an Intent to start the YubiKeyPromptActivity. The action returns a result which the Activity will pass back to the caller. Arguments to the action can be passed as extras to the Activity. The YubiKeyPromptConnectionAction class can be used when a specific type of connection is required:

//MyAction.java
public class MyAction extends YubiKeyPromptConnectionAction<SmartCardConnection>(SmartCardConnection.class) {
  @Override
  void onYubiKeyConnection(SmartCardConnection connection, Bundle extras, CommandState commandState, Callback<Pair<Integer, Intent>> callback) {
    // Read out a certificate using the PIV module:
    PivSession session = new PivSession(connection);
    X509Certificate certificate = session.getCertificate(Slot.AUTHENTICATION);
    Intent result = new Intent();
    result.putExtra("EXTRA_CERTIFICATE", certificate.getEncoded());
    callback.invoke(new Pair<>(Activity.RESULT_OK, result));
  }
}

The OTP Activity

A specialized YubiKey Prompt Activity used to read out an OTP over the keyboard interface (or from the NFC NDEF payload). It does not require a separate Action.

startActivityForResult(new Intent(context, OtpActivity.class), requestCode);

...

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
  if(resultCode == Activity.RESULT_OK) {
    String otp = data.getStringExtra(OtpActvity.EXTRA_OTP);
  }
}

Overriding resources

Client applications can override colors and string resources of the YubiKey Prompt and OTP activities.

For example, to change a background color of the yubikit prompt dialogs, add a color resource to your application:

<resources>
    <color name="yubikit_dialog_background">#DEBF90</color>
</resources>

We don’t recommend overriding resources which are not marked as public.