Create a UCM plugin
Last updated February 8th, 2024
A significant benefit of the UCM framework is that it enables storage vendors to develop a plugin which provides access to their storage space and cryptographic operations without forcing app developers to change their code or forcing end users to update their apps. This section explains how a storage vendor can create such a plugin for the UCM framework.
Overview
Implementing a storage plugin is comprised of the following tasks:
- Select the capabilities of the plugin
- Providing a package with a service that extends
UCMAgentService
- Implementing at least one service provider interface
- Supplying the credential_agent.xml file
- Supplying an Android manifest file which references the credential_agent.xml file as well as the required tags to support a specific intent
After completing these steps, deliver your plugin as an APK.
Plugin capabilities
A UCM plugin controls the following:
- Whether or not the plugin is configurable by an MDM agent or ISV app.
- Whether or not the plugin is manageable by an MDM agent or ISV app.
- A plugin cannot be configurable and have manageable set to false. Select one of the other three alternatives.
- If you want to store keys for On-Device Encryption, both of the above properties should be false.
- If specific cryptographic properties are required for a specific app, both options should be set to true.
See Define the plugin properties for more information on setting these parameters.
UCMAgentService class
As the storage vendor, you must provide a service class which extends UCMAgentService
. This service defines all the interfaces for your plugin. The parent class for this service is — com.samsung.android.knox.ucm.plugin.agent.UcmAgentService
.
There are a number of methods which are annotated for overrides in the UCM Agent Service (UCMAgentService
) base class. The following list organizes these methods into categories. The number of methods that are overridden is determined, in large part, by the features of the plugin. The following methods must be overridden as is appropriate for the plugin’s functionality:
getCredentialStorageProperty
setCredentialStorageProperty
notifyChange
configureCredentialStoragePlugin
getCredentialStoragePluginConfiguration
changePin
verifyPin
verifyPuk
getStatus
generateDek
getDek
generateWrappedDek
unwrapDek
generateKeyguardPassword
getDetailErrorMessage
APDUCommand
initKeyguardPin
setKeyguardPinMaximumRetryCount
setKeyguardPinMinimumLength
setKeyguardPinMaximumLength
getKeyguardPinMaximumRetryCount
getKeyguardPinCurrentRetryCount
getKeyguardPinMinimumLength
getKeyguardPinMaximumLength
Create a service provider interface
The package com.samsung.android.knox.ucm.plugin.agent
includes overridden standard service-provider interfaces such as CipherSpi
(with UcmCIpherSpi
) and KeyPairGeneratorSpi
(with UcmAgentKeyPairGeneratorSpi
). Your plugin must implement at least one of these UCM SPIs in order for UCM to identify your plugin and use it to access your plugin storage and crypto functions.
The UCM plugin template contains example wrapper classes, such as the following, which you can use to register plugin implementation logic for SPIs.
KeyStoreProvideService
CipherEseService
KeyPairGeneratorService
SecureRandomService
SimpleSignatureService
Define the plugin properties
Define your plugin’s properties through a file called credential_agent.xml, which can include the following metadata:
Key | Value | Description | Additional Info |
---|---|---|---|
id | String | Plugin id. This value is used as the provider name. | |
summary | String | Plugin summary. Additional information about the plugin. | |
title | String | Plugin title. | |
vendorId | String | Vendor id. | |
isGeneratePasswordSupport | true/false |
true — UCM Keyguard is supported false — UCM Keyguard is not supported |
|
isODESupport | true/false |
true — UCM ODE is supported false — UCM ODE is not supported |
|
isManageable | true/false |
true — plugin is manageable false — plugin is not manageable |
|
enforceManagement | true/false |
true — plugin should be managed false — plugin need not to be managed |
|
pinMinimum | Number | Minimum length of PIN | Required only if ODE is supported. |
pinMaximum | Number | Maximum length of PIN | |
pukMinimum | Number | Minimum length of PUK | |
pukMaximum | Number | Maximum length of PUK | |
pinRetrycount | Number | If user input wrong PIN more than 'pinRetrycount', Keygaurd / ODE request PUK. | |
AID | Number(HEX) | Applet AID. | |
enabledSCP | true/false |
true — SCP is enabled false — SCP is not enabled |
|
supportSign | true/false |
true — plugin support signature SPI false — plugin support signature by Cipher SPI |
|
supportPinConfiguration | true/false |
true — plugin supports pin configuration functions (init PIN , set PIN min/max length, set max retry count) false — plugin doesn't support pin configuration functions |
|
isSupportChangePin | true/false |
true — plugin supports changePin from Settings false — plugin doesn't support changePin from Settings |
|
isSupportBiometricForUCM | true/false |
true — plugin allows biometric(Ex.fingerPrint) if UCM Keyguard configured with this Plugin false — plugin doesn't allow biometric(Ex.fingerprint) if UCM Keyguard configured with this plugin |
The following is a sample credential_agent.xml file. Your version is not likely to define all the attributes as we’ve done here for the sake of completeness. Select the appropriate attributes for your storage type and requirements, then see the following sections for the attribute definitions and accepted values.
<?xml version="1.0" encoding="utf-8"?>
<cred-agent
id="My Plugin Agent - ID"
summary="This is My Plugin Agent"
title="My Plugin Agent - title"
vendorId="Test Vendor"
pinMinimum="4"
pinMaximum="6"
pukMinimum="8"
pukMaximum="8"
pinRetrycount="5"
isGeneratePasswordSupport="true"
isODESupport="false"
storageType="ETC"
isManageable="true"
enforceManagement="false"
enabledSCP="NONE"
AID="A0B1C2D3E4F5ABCD">
</cred-agent>
Set up the Android Manifest file
The following is a sample manifest file which includes the required references to the UCM service class:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ucm.example.myucmplugin">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".MyUcmPluginService"
android:enabled="true"
android:exported="true"
android:permission="com.samsung.android.knox.permission.KNOX_UCM_BIND_PLUGIN_SERVICE">
<intent-filter>
<action android:name="com.samsung.android.knox.intent.action.UCM_AGENT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="com.samsung.ucm.agent"
android:resource="@xml/plugin_info" />
</service>
</application>
</manifest>
Create the UCM plugin
Here is some sample code showing how to create a plugin:
package ucm.example.myucmplugin;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import com.samsung.android.knox.ucm.plugin.agent.UcmAgentProviderImpl;
import com.samsung.android.knox.ucm.plugin.agent.UcmAgentService;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import javax.crypto.NoSuchPaddingException;
import ucm.example.myucmplugin.crypto.MyCipherSpi;
import ucm.example.myucmplugin.crypto.MyKeyPairGeneratorSpi;
import ucm.example.myucmplugin.crypto.MyKeystoreSpi;
import ucm.example.myucmplugin.crypto.MySecureRandomSpi;
import ucm.example.myucmplugin.crypto.MySignatureSpi;
public class MyUcmPluginService extends UcmAgentService {
private final static String TAG = "MyUcmPluginService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
initializeProvider();
}
private void initializeProvider() {
UcmAgentProviderImpl provider = (UcmAgentProviderImpl) getProvider();
provider.putServiceImpl(new MyKeyStoreProviderService(provider, UcmAgentProviderImpl.KEYSTORE, UcmAgentProviderImpl.KEYSTORE_TYPE, MyKeystoreSpi.class.getName(), this));
provider.putServiceImpl(new MyCipherService(provider, UcmAgentProviderImpl.CIPHER, UcmAgentProviderImpl.CIPHER_RSA_ECB_PKCS1PADDING, MyCipherSpi.class.getName(), this));
provider.putServiceImpl(new MyCipherService(provider, UcmAgentProviderImpl.CIPHER, UcmAgentProviderImpl.CIPHER_RSA_ECB_NOPADDING, MyCipherSpi.class.getName(), this));
provider.putServiceImpl(new MyKeyPairGeneratorService(provider, UcmAgentProviderImpl.KEYPAIRGENERATOR, UcmAgentProviderImpl.KEYPAIRGENERATOR_RSA, MyKeyPairGeneratorSpi.class.getName(), this));
provider.putServiceImpl(new MvSecureRandomService(provider, UcmAgentProviderImpl.SECURERANDOM, UcmAgentProviderImpl.SECURERANDOM_SHA1PRNG, MySecureRandomSpi.class.getName(), this));
provider.putServiceImpl(new MySignatureService(provider, "Signature", "sha256WithRSA", MySignatureSpi.class.getName(), this));
}
private static final class MyKeyStoreProviderService extends Provider.Service {
private final Context mCtx;
public MyKeyStoreProviderService(Provider provider, String type, String algorithm, String className, Context ctx) {
super(provider, type, algorithm, className, null, null);
mCtx = ctx;
}
@Override
public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException {
return new MyKeystoreSpi(mCtx);
}
}
private static final class MyCipherService extends Provider.Service {
private final Context mCtx;
private final String mAlgorithm;
public MyCipherService(Provider provider, String type, String algorithm, String className, Context ctx) {
super(provider, type, algorithm, className, null, null);
mCtx = ctx;
mAlgorithm = algorithm;
}
@Override
public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException {
try {
return new MyCipherSpi(mCtx, mAlgorithm);
} catch (NoSuchPaddingException e) {
Log.e(TAG, "Fail to create new MyCipherSpi... " + e.getMessage());
e.printStackTrace();
}
return null;
}
}
private static final class MyKeyPairGeneratorService extends Provider.Service {
private final Context mCtx;
private final String mAlgorithm;
public MyKeyPairGeneratorService(Provider provider, String type, String algorithm, String className, Context ctx) {
super(provider, type, algorithm, className, null, null);
mCtx = ctx;
mAlgorithm = algorithm;
}
@Override
public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException {
return new MyKeyPairGeneratorSpi(mCtx, mAlgorithm);
}
}
private static final class MvSecureRandomService extends Provider.Service {
private final Context mCtx;
public MvSecureRandomService(Provider provider, String type, String algorithm, String className, Context ctx) {
super(provider, type, algorithm, className, null, null);
mCtx = ctx;
}
@Override
public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException {
return new MySecureRandomSpi(mCtx);
}
}
private static final class MySignatureService extends Provider.Service {
private final Context mCtx;
public MySignatureService(Provider provider, String type, String algorithm, String className, Context ctx) {
super(provider, type, algorithm, className, null, null);
mCtx = ctx;
}
@Override
public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException {
return new MySignatureSpi(mCtx);
}
}
@Override
public Bundle getCredentialStorageProperty(int adminUid, int userId, Bundle args) {
return null;
}
@Override
public Bundle setCredentialStorageProperty(int adminUid, int userId, Bundle args) {
return null;
}
@Override
public int notifyChange(int eventId, Bundle data) {
return 0;
}
@Override
public Bundle configureCredentialStoragePlugin(int adminUid, Bundle profile, int requestId) {
return null;
}
@Override
public Bundle getCredentialStoragePluginConfiguration(int adminUid) {
return null;
}
@Override
public Bundle verifyPin(int userId, String pin, Bundle extras) {
return null;
}
@Override
public Bundle verifyPuk(String puk, String pin) {
return null;
}
@Override
public Bundle changePin(String oldPin, String newPin) {
return null;
}
@Override
public Bundle setState(int state) {
return null;
}
@Override
public Bundle getStatus() {
return null;
}
@Override
public Bundle generateDek() {
return null;
}
@Override
public Bundle generateWrappedDek() {
return null;
}
@Override
public Bundle getDek() {
return null;
}
@Override
public Bundle unwrapDek(byte[] wrappedDek) {
return null;
}
@Override
public Bundle generateKeyguardPassword(int userId, Bundle extras) {
return null;
}
@Override
public Bundle getInfo() {
return null;
}
@Override
public Bundle APDUCommand(byte[] apdu, Bundle extras) {
return null;
}
@Override
public String getDetailErrorMessage(int errorCode) {
return null;
}
@Override
public Bundle initKeyguardPin(String initPin, Bundle bundle) {
return null;
}
@Override
public Bundle setKeyguardPinMaximumRetryCount(int retryCount) {
return null;
}
@Override
public Bundle setKeyguardPinMinimumLength(int minimumLength) {
return null;
}
@Override
public Bundle setKeyguardPinMaximumLength(int maximumLength) {
return null;
}
@Override
public Bundle getKeyguardPinMaximumRetryCount() {
return null;
}
@Override
public Bundle getKeyguardPinCurrentRetryCount() {
return null;
}
@Override
public Bundle getKeyguardPinMinimumLength() {
return null;
}
@Override
public Bundle getKeyguardPinMaximumLength() {
return null;
}
}
On this page
Is this page helpful?