Integrating SmartLicence into your Android app

This section describes how to integrate SmartLicence into your application. An example application is also provided with your digital on-boarding assets. This application shows you how to use SmartLicence, including customising the user interface (discussed in the next section).

Add the dependencies

Add the smartlicence-android-1.0.5.aar Android library to your applications libs folder, typically in app/libs and the BouncyCastle dependencies.

Add the dependencies to the application’s gradle file (app/build.gradle):

dependencies {
  implementation fileTree(include: ['*.aar'], dir: 'libs')
  implementation 'org.bouncycastle:bcpkix-jdk15to18:1.68'
  implementation 'org.bouncycastle:bcprov-jdk15to18:1.68'
  // other app dependencies..
}

Add the public key to the assets folder

Add the vendor-product.pub public key to the assets folder app/src/main/assets.

Attempt to obtain the device’s serial number

Note

This section is not required for per-installation based activations.

You only need the device’s serial number to support re-activation of software on the same device following a factory reset.

Android only allows device or profile owner applications to access the device’s serial number. These are special applications that manage the device or work profile. Regular applications cannot read the device serial number.

Option 1: Device or profile owners

If you app is a device or profile owner, then the serial number is obtained via the Build class:

String serialNo;
if (Build.VERSION.SDK_INT >= 26) {
  serialNo = Build.getSerial();
} else {
  serialNo = Build.SERIAL;
}

Option 2: Apps that may run on devices that use Xewli’s Tactical Device Manager

If your app is not an Android device or profile owner, it may still obtain the device’s serial number if Xewli’s Tactical Device Manager is installed. The SerialNumberClient class is used to attempt to obtain the serial number and provides a method for falling back if it’s not available.

Add a member variable to your activity:

private SerialNumberClient serialNumberClient;

In your activity’s onCreate() method, use the SerialNumberClient to attempt to obtain the serial number:

serialNumberClient = new SerialNumberClient(this);
serialNumberClient.fetch(
    new Callback() {
      @Override
      public void onSuccess(String serialNumber) {
        MyActivity.this.serialNumber = serialNumber;
        switchToActivationFragment();
      }

      @Override
      public void onUnavailable() {
        // Fallback to per-installation activation using a random nonce
        MyActivity.this.serialNumber = UUID.randomUUID().toString();
        switchToActivationFragment();
      }
    });

Integrate the activation fragment

SmartLicence provides the fragment ActivationFragment for handling the activation process.

You create a new instance of the fragment, specifying your Vendor ID and Application ID and a unique identifier appropriate to the activation type.

In your desired Activity, instantiate the fragment using the provided factory method:

private void switchToActivationFragment() {
  activationFragment =
      ActivationFragment.newInstance(
          VENDOR_ID, APPLICATION_ID, MyActivity.this.serialNumber);

  getSupportFragmentManager()
      .beginTransaction()
      .replace(R.id.fragment_holder, activationFragment)
      .addToBackStack(null)
      .commit();
}

Validate the licence

The androidx.lifecycle.Observer class is used to inform your activity when the smart card has issued the licence. Define a new observer in your Activty using the example as a starting point:

private final Observer<Licence> licenceObserver = licence -> {
  try {
    InputStream inputStream = getApplicationContext().getAssets().open("vendor-product.pub");
    RSAPublicKey rsaPublicKey = PemUtils.readPublicKey(inputStream);

    if (licence.isValid(rsaPublicKey, MyActivity.this.serialNumber)) {
      // Product is licenced, store the result and notify the user
      storeLicence(licence);
      startAnotherActivityOrReplaceWithAnotherFragment();
    } else {
      // Product is not licenced, notify the user
      Toast.makeText(MyActivity.this, "Not licensed", Toast.LENGTH_LONG).show();
    }
  } catch (IOException
      | InvalidKeySpecException
      | NoSuchAlgorithmException
      | InvalidKeyException e) {
    // The device doesn't support RSA, SHA256 or there is something wrong with the key
    Toast.makeText(MyActivity.this, "Unable to validate the licence", Toast.LENGTH_LONG)
        .show();
  } catch (SignatureException e) {
    // This is a card problem or malicious attempt to break the product
    Toast.makeText(MyActivity.this, "Signature is invalid", Toast.LENGTH_LONG).show();
  }
};

You should tailor the actions taken for the following scenarios:

  • Successful activation.
  • There being no more licences remaining on the card;
  • The device doesn’t support the digital signature verification mechanisms or the public key is invalid; and
  • The signature is invalid.

Register the observer in your Activity’s onCreate() using the ViewModelProvider:

SmartLicenceViewModel smartLicenceViewModel =
    new ViewModelProvider(this).get(SmartLicenceViewModel.class);
smartLicenceViewModel.getLicence().observe(this, licenceObserver);

Licence features

SmartLicence supports an optional feature mask. This can be used to differentiate between product versions or to enable up to 12 independent features.

short FEATURE_MASK_PROFESSIONAL_EDITION = (short) 1 << 2;
boolean isProfessionalEdition =
    ((Licence.getFeatureMask() & FEATURE_MASK_PROFESSIONAL_EDITION) != 0);

SmartLicence reserves two additional feature bits:

// SmartLicence will force per-installation mode
// Unique identifiers will be used only as nonces to prevent replay-attacks
final short RESERVED_FEATURE_MASK_PER_INSTALLATION_MODE = (short) 1 << 13;

// SmartLicence card is a developer card
// Every activation attempt will be successful if the Vendor ID and Product ID match
final short RESERVED_FEATURE_MASK_DEVELOPMENT_MODE = (short) 1 << 14;

Using the licence

Once the licence signature has been verified, the application should become fully usable. There are three options for managing your full functionality.

Option 1: Enable components

The first approach is to disable the activation components and enable to feature components:

// Disable activation component
getPackageManager().setComponentEnabledSetting(activationActivity,
    PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

// Enable feature component
getPackageManager().setComponentEnabledSetting(featureActivity,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

Option 2: Check SharedPreferences just before use

The second approach is to store the state in a shared preference and check it before the a given feature is used:

// Store the result
getSharedPreferences("mylicence", Context.MODE_PRIVATE).edit()
    .putBoolean("is_activated", true)
    .apply();

// Later check the result
boolean isActivated = getSharedPreferences("mylicence", Context.MODE_PRIVATE)
    .getBoolean("is_activated", false);

Option 3: Store and recheck the licence signature

SmartLicence also provides an ActivationRecord class for storing and later re-validating the digital signature:

// Store the result
new ActivationRecord(getApplicationContext()).saveLicence(licence);

// Validate the result later
boolean isActivated = new ActivationRecord(getApplicationContext())
  .isActivated(VENDOR_ID, APPLICATION_ID, serialNo, publicKey);

Only allow development cards to be used with debug builds

You can protect your IP from loss of a development card by only accepting licences generated by one in a debug build variant:

boolean shouldRejectLicence = !BuildConfig.DEBUG && licence.isDebugLicence();

Android Backup

If you application supports Android Backup, and you use a simple preferences file then you should exclude it from the backup.

The ActivationRecord class stores the licence signature in the preference licence.xml. However, if it is used with the device’s serial number and the isActivated() call dynamically fetches the current device’s serial number, then this will correctly fail if the backup is restored on a different device and pass if the backup is restored on the original device.