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.