Create a UCM plugin
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.
Note:
- 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 meta data 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 overriden as is appropriate for the plugin's functionality:
getCredentialStorageProperty
setCredentialStorageProperty
notifyChange
configureCredentialStoragePlugin
getCredentialStoragePluginConfiguration
changePin
verifyPin
verifyPuk
getStatus
generateDek
getDek
generateWrappedDek
unwrapDek
generateKeyguardPassword
getDetailErrorMessage
APDUCommand
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 stoage 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 |
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; } }