Back to top

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;
  }
  
}

Is this page helpful?