GitHubContribute in GitHub: Open doc issue|Edit online

CC ACSP Java Programmer's Guide

IBM Unified Key Orchestrator Crypto Connect ACSP is a solution for providing remote hardware based cryptographic services to your applications.

This document covers the Java programming interfaces to the CC ACSP client for the application programmer.

The IBM Unified Key Orchestrator Crypto Connect ACSP solution supports IBM Common Cryptographic Architecture (CCA), which is a common interface for IBM Hardware Security Modules (HSM). It is available on most platforms provided by IBM, and on Linux and Windows platforms. The applications which require Cryptographic Services use the IBM Java API for CCA (jCCA). jCCA is a Java Framework that is based on the Java Native Interface (JNI), which enables the CCA programming interface from the Java programming environment. jCCA requires a Hardware Security Module (HSM), such as an IBM 4764, IBM 4765, or IBM 4767 Cryptographic Coprocessor installed on the platform, for example the Linux SUSE or RedHat distribution. jCCA has also support for ICSF under z/OS. This interfaces with the Cryptographic Express Accelerators (CEX3, CEX4, CEX5, CEX6 and CEX7) which are available in system z Enterprise Mainframe Environments.

The IBM Unified Key Orchestrator Crypto Connect ACSP product includes the jCCA, but adds a client / server infrastructure. This extends the jCCA Framework executed from remote clients. The CC ACSP client can in principle be interfaced from any platform supporting Java Runtime Environment (JRE). Examples are Mac OSX, Solaris, HP UNIX etc.

The IBM Unified Key Orchestrator Crypto Connect ACSP has also support for PKCS#11, nicknamed Cryptoki, which enables use of the standard Java Cryptographic Extensions (JCE) provider.

It containes information about

  • jCCA Programming
  • Usage of JCE with CC ACSP
  • CC ACSP User Defined Functions

There are also user defined functions (UDF) “Sample Program Listings” provided in the installation package and in this document.

In addition to these chapters, the CCA and PKCS#11 Programmer's Guide describes relevant considerations when programming with IBM Unified Key Orchestrator Crypto Connect ACSP.

jCCA Programming

To use the CC ACSP solution feature, the main interface is the IBM Java interface to CCA (jCCA).

This chapter describes how to setup clients using the CC ACSP server.

CC ACSP Developer Client Package

The CC ACSP Developer Client packageis a generic CC ACSP Java Client configured for use with the CC ACSP client sample setup.

The acsp-developer-2.1.5.zip file contains the following:

Directory File Description
doc acsp-java-client-2.1.5.3-read-me.txt CC ACSP client read me information
acsp-monitor--2.1.5.3read-me.txt CC ACSP monitor read me information
jcca-1.6.7.3.read-me.txt jCCA read me information
jcca-1.6.7.3-changes.txt jCCA change list
jcca-1.6.7.3-javadoc.jar jCCA Java Documentation
lib acsp-java-client-2.1.5.3.jar CC ACSP Java Client library
acsp-java-common-2.1.5.3.jar CC ACSP Java common library
acsp-monitor-2.1.5.3.jar CC ACSP monitor jar for JMX programming
jcca-1.6.7.3.jar Java jCCA 1.6.7.3 library
Jcca-1.6.8.3.jar Java jCCA 1.6.8.3 library
log4j-api-2.17.1.jar Java jLog4j2 2.17.1 api library
log4j-core-2.17.1.jar Java jLog4j2 2.17.1 core library
log4j-jul-2.17.1.jar Java jLog4j2 2.17.1 jul library
license acsp-license-information.2.1.5.txt License information
acsp-notices.2.1.5.txt International License Agreement for Non-Warranted Programs, Notices and information
sample Contains Java sample programs for reference.
(root) acsp.properties Sample acsp properties file
acsp.sample.properties Sample acsp properties file
ivp.client_1.ks ivp.client_2.ks ivp.client_3.ks Sample client keystores
ivp.server.ts Trust store for CA certificates
jcca.properties Sample jCCA properties using CC ACSP
log4j2.properties Sample Log4J2 properties file

Before Starting to Use jCCA

jCCA is a Java Framework which wraps the CCA interface for use with Java. The jCCA contains Java documentation of the wrapped functions, which explains the parameters of the CCA verbs supported, but jCCA does not explain how to use CCA. You will therefore need some background information of how to use CCA.

If you are not acquainted with CCA, the introduction in Chapter 1 of the “CCA Basic Services Reference and Guide for the IBM 4770, IBM 4769, IBM 4767, and IBM 4765 PCIe Cryptographic Coprocessors Releases 8.4, 7.5, 7.4, 7.3, 7.2, 5.7, 5.6, 5.5, 5.4, 5.3, 4.4, and 4.2“ is a good place to start. It describes how applications obtain service, but only from native programs. For z/OS, CCA is implemented in ICSF where the manual “SC14-7508 z/OS ICSF Application Programmers Guide” is the best source.

To understand how you call CCA from Java, you must consult the Javadoc for jCCA delivered with CC ACSP Java Client. You should know that when you call a standard CCA function, it is transparent to your application program if the call is executed locally in an IBM 4767, or IBM 4769 cryptographic coprocessor, IBM z/OS ICSF, or sent to a CC ACSP server with native access to the IBM Coprocessor hardware.

Using jCCA Directly to Interface with the IBM 476x Coprocessor

The jCCA package contains a Java framework to interface the Hardware Cryptographic Coprocessor installed on supported platforms. In this configuration the jCCA requires that the IBM 476x Cryptographic Coprocessor and driver are installed on the computer platform to gain access from Java. This is equivalent to the CCA interface in z/OS implemented by the Integrated Cryptographic Service Facility (ICSF) as interface to the IBM Cryptographic Hardware Accelerators (CPACF, CEX4, CEX5, CEX6, CEX7 or CEX8).

To enable access from Java, you have to:

  1. Install the jCCA JNI DLL file to interface with the IBM 476x Coprocessor for the appropriate execution platform from Java (please look in jCCA read.me file).
  2. Install the jcca-v.r.m.s.jar file in the Java class path.

The jCCA framework defines all the verbs for the IBM 476x Cryptographic Coprocessor verbs, and on the z/OS platform it defines all the cryptographic services as defined by the ICSF. The jCCA Java documentation describes these.

Note: The jCCA JNI DLL files are not delivered with the CC ACSP Java Client, only with the CC ACSP server distribution. If the jCCA native library is required, the jCCA installation package must be acquired and installed separately.

Using CC ACSP client with jCCA

When using CC ACSP client in conjunction with the jCCA, the physical access to the IBM 476x Cryptographic Coprocessor is not required. Instead of using the DLL to interface to the IBM 476x Cryptographic Coprocessor or the Cryptographic Express 4, Express 5, Express 6 or Express 7 in the z/OS ICSF or Linux on z environments, we use the CC ACSP Java Client implementation in the jCCA, which uses the HSM environment via the CC ACSP server.

Compared to the jCCA native installation, we do not need the JNI DLL files on a jCCA/CC ACSP client, which instead establishes a connection to the CC ACSP server via a network interface. This also makes the client platform independent of the HSM platform requirements.

All we need to do is:

  1. Install the jar files delivered with the acsp-developer-2.1.5.zip file in the Java class path
  2. Configure jCCA with the CC ACSP Java Client configuration file (jcca.properties)
  3. Configure the CC ACSP server with the CC ACSP client configuration file (acsp.properties)

The jCCA framework reads the file 'jcca.properties' as its default. If the file is placed in the same directory as the jcca-v.r.m.s.jar file, it is not necessary to specify the -Djcca= parameter.

We need to tell the jCCA framework that it should use the CC ACSP server instead of using the default jCCA direct interface. To do this we must specify a jCCA configuration parameter in the 'jcca.properties' file, which identifies CC ACSP client as provider for the client.

The parameter looks like this:

cca-root-class=com.ibm.acsp.cca.ProtocolCcaClient

The CC ACSP client reads the file acsp.properties as its default. If the file is placed in the same directory as the acsp-java-client-v-r-m.jar, it is not necessary to specify the -Dacsp= parameter.

When this parameter is specified on the client side, the CC ACSP client will be used instead of the default direct interface to the IBM 476x Cryptographic Coprocessor or the Cryptographic Express 4, Express 5, Express 6 or Express 7 in the z/OS ICSF or Linux for z environment.

Configure CC ACSP client

The actual CC ACSP client configuration parameters are described in the Client installation.

In general we only need to specify the host name of the CC ACSP server machine, and the auto-configuration feature in the CC ACSP server will pick up the ports we have to use for the CC ACSP protocols from the server.

The following is a short explanation of the CC ACSP client properties needed:

client.autoconf.host = acsp.server.example.com This field defines the target CC ACSP server host name.

client.autoconf.port = 9901

This field defines the ACP port on the CC ACSP server host.

client.autoconf.transport = tls

This field tells the CC ACSP client to use SSL/TLS security for the connection.

ssl.trustStore = ivp.client_1.ks

This field tells the CC ACSP client to use client1 trust-store for server certificates. It is the same as the keyStore.

ssl.keyStore = ivp.client_1.ks ssl.keyStorePassword = zaqwsx

These fields tells the CC ACSP client to use client1 key-store for the client private key and certificate and the password for this.

The above is basically what you need in order to use the CC ACSP client with the CC ACSP server. To make it work you should only change the host name of the CC ACSP server in the distributed configuration file.

Configure CC ACSP Client in Eclipse

For Java development Eclipse Foundation IDE are often used. The following is a walk-through explaining how to create and configure an Eclipse project to use the jCCA with the CC ACSP client to connect to a CC ACSP server. A certain level of Eclipse and Java knowledge is required.

You will need the content of the acsp-developer-2.1.5.zip file, so make sure that you have unpacked the file to your local file system before you start. We will be using the Mac sample Java program listed in Appendix A of this manual.

  1. Start Eclipse.
  2. Create a new Java project: new->Java->Java Project.
  3. Name it “ACSP Test Client” as project name: Finish.
  4. Select “ACSP Test Client” in the package Explorer view.
  5. Right Click->new folder: name it “lib” and press Finish.
  6. Select the lib folder, right click and choose import->General->file system and next.
  7. From the unpacked acsp-java-client folder's lib directory import all the jar files. Make sure the “create top level folder” is deselected and press the Finish button.
  8. Select “ACSP Test Client” again, right click and select Build Path->Configure Build Path.
  9. Select “Libraries tab” and the “Add jar” button
  10. Select the “ACSP Test Client” project and the lib subdirectory and select and press OK for each of the jar files which are listed in step 7. Press OK when done.
  11. Now we will add the configuration files to the root of the “ACSP Test Client” project.
  12. Select the “ACSP Test Client” project, right click and choose import->General->File System and Next.
  13. From the unpacked acsp-client folder import the following files: jcca.properties, acsp.properties, ivp.client_1.ks and log4j2.properties. Make sure the “create top level folder” is deselected and press the Finish button.
  14. Now add a new class, called Mac.java, to the source or src folder by:
    • selecting the source folder.
    • Right click and select: new->class,
    • name it: Mac and press the Finish button.
  15. Copy and paste the Mac sample code.
  16. Now run the Mac Java code without parameters:
    • select “Mac.java”,
    • right click and select: Run as->Java Application.
  17. Make sure that the acsp.properties have the correct server configuration settings and host name.
  18. The console now shows the log from acsp and the log from the Mac sample program.

The above project “ACSP Test Client” can now be used to run jCCA programs by using the CC ACSP client against the configured CC ACSP server from the same project, but also from other Eclipse Java projects.

To Run a jCCA program using the above CC ACSP client configuration, do the following:

  1. Select the other Java project and right click.
  2. Choose Build Path → Configure Build Path.
  3. Click Projects.
  4. Click and select the “ACSP Test Client” project and click Ok,
  5. Click Ok.
  6. Select the Java package/program in the other Java project which should be executed and right click.
  7. Select Run As → Java Application The program fails; but the program is defined in the Run configurations.
  8. Select Run As → Run Configurations.
  9. A configuration window is shown for the program. Now select the arguments tab, and for the “Working directory” select “other”.
  10. Press the Workspace button, select the “ACSP Test Client” project and press OK.
  11. Now press the Apply button and then the Run button.
  12. The programs now run using the “ACSP Test Client” configurations, and with the “ACSP Test Client” as current directory

jCCA Example

When all the above configurations have been created, you are ready to create some sample code to call CCA verbs using jCCA and the CC ACSP.

Try the following example which calls the One Way Hash CCA function:

CSNBOWH bow = new CSNBOWH();
try {
    bow.addRule(CSNBOWH.SHA256);
    bow.addRule(CSNBOWH.ONLY);
    bow.setText("hash text".getBytes());
    bow.execute();
    System.out.println("hash retrieved = " + 
        CCAUtil.B2X(bow.getHash()));
} 
catch (CCAException ce) {
    System.out.println("hash failed - exception" + ce); 
}

CC ACSP and Java JCE

The IBM Unified Key Orchestrator Crypto Connect ACSP does not provide any new Java JCE implementations, however, it is possible to use the following providers by using the CC ACSP clients:

  • jCCA embedded JCE provider with limited functionality.
  • Standard JRE from IBM and Oracle, JCE using PKCS#11

This chapter describes the setup, usage and known limitations of JCE when using it with CC ACSP clients.

Note: Knowledge of JCE programming is required.

jCCA Embedded JCE Provider

The IBM Java API to CCA (jCCA) includes an embedded JCE (JCA) provider, which implements a limited set of functions using the jCCA Framework.

The functions in the embedded provider are limited to functions allowed by an unsigned JCE provider, which in general means:

  • Digesting functions (hashing)
  • Key generation for digital signatures (RSA, DSA, ECC)
  • Digital signatures

Standard Edition JCE Provider PKCS#11 Support

The JCE implementation in the standard editions of the SDK provide support for PKCS#11. This topic explains how to use the CC ACSP client implementations with the IBM JDK and the Oracle JDK PKCS#11 JCE providers.

Using CC ACSP PKCS#11 Client from java requires installation of: CC ACSP CCA Client - and either - CC ACSP PKCS#11 CCA Client - or - CC ACSP PKCS#11 EP11 Client or - CC ACSP PKCS#11 Client

To use strong keys the “Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files” must be downloaded from the website oracle.com or ibm.com.

References

Used Classes

KeyStore A class used to read and load a keystore. Same for both JKS and PKCS11 keystores
Provider Use SunPKCS11 for Oracle JRE or use IBMPKCS11Impl for IBM JRE.
KeyStore.Entry This is returned by keyStore.getEntry(String, ProtectionParameter) Three basic KeyStore.Entry implementations are provided within the JRE.
KeyStore.PrivateKeyEntry This type of entry holds a cryptographic private key, which is optionally stored in a protected format to prevent unauthorized access. It is also accompanied by a certificate chain for the corresponding public key
KeyStore.SecretKeyEntry This type of entry holds a cryptographic secret key, which is optionally stored in a protected format to prevent unauthorized access
KeyStore.TrustedCertificateEntry This type of entry contains a single public key certificate belonging to another party. It is called a trusted certificate because the key store owner trusts that the public key in the certificate belongs to the identity which is identified by the subject and owner of the certificate.

Configuration of a PKCS #11 Provider

See reference [1] for the JCE pkcs11 provider. Here is used the ”Sun PKCS#11” provider, which is implemented by the main class sun.security.pkcs11.SunPKCS11. The provider uses a PKCS11 dynamic link library (DLL). The provider uses a customized configuration file, which here is called java-cca.cnf, and can handle several DLL's at the same time.

Using a Static Provider Configuration:

Add a line in the file %JAVA_HOME%/lib/security/java.security as follows:

security.provider.n=sun.security.pkcs11.SunPKCS11 /path-to/java-cca.cnf

In this n is the next number in the series, and path-to can be any path as well as the name of the file.

Example of java-cca.cnf:

name=CCA-0
library={full path to}/acsp-pkcs11_ep11.dll
slot=0

Or a Dynamic Configuration:

A dynamic configuration is done in code alone. It requires the name of the DLL (full path) and slot ID, if different from slot 0.

Java Example for Oracle/SUN PKCS#11:

String PROVIDER_SUNPKCS11 = "sun.security.pkcs11.SunPKCS11";
Class<Provider> clazz = (Class<Provider>)Class.forName(PROVIDER_SUNPKCS11);
Constructor<Provider> ctor = clazz.getConstructor(new Class[] {InputStream.class});
String DLLname = "path/pkcs11_cca.dll";
String slot = "0";

// Initialize for SunPKCS11 implementation
String conf = "name = CCA-0" + "\nlibrary=" + DLLname + "\nslot="+slot;
ByteArrayInputStream config = new ByteArrayInputStream(conf.getBytes());
p = (Provider)ctor.newInstance(new Object[] {conf});

// Next add the provider as the first in the list:
Security.insertProviderAt(p, 1);

Java example for IBM PKCS#11:

String PROVIDER_IBMPKCS11 = "com.ibm.crypto.pkcs11impl.provider.IBMPKCS11Impl";
Class<Provider> clazz = (Class<Provider>)Class.forName(PROVIDER_IBMPKCS11);
Constructor<Provider> ctor = clazz.getConstructor(new Class[] {BufferedReader.class});
String DLLname = "path/pkcs11_cca.dll";
String slot = "0";

// Initialize for IbmPKCS11 implementation
String conf = "name = CCA-0" + "\nlibrary=" + DLLname + "\nslot="+slot;
BufferedReader config = new BufferedReader(new StringReader(conf));
p = (Provider)ctor.newInstance(new Object[] {conf});

Loading Key Store

A key store must be loaded before it can be used. This is the only difference (in application code) between a Java key store (JKS) and a PKCS #11 key store.

There are two ways to get the key store, either

  • by type - when the provider is defined as the first provider in the list of providers with pkcs11 support
  • by name - Name as defined in configuration, see later.

Getting Key Store by Type

KeyStore ks= KeyStore.*getInstance*(”pkcs11”);
or
KeyStore ks= KeyStore.*getInstance*(”JKS”);

Getting Key Store by Name

Provider p = Security.getProvider("SunPKCS11-CCA-0");
KeyStore ks= KeyStore.getInstance("pkcs11", p);

For JKS key stores a file name and path to the JKS file and a password, the following statements are used:

FileInputStream s = new FileInputStream(fileName);
ks.load(s, ivKeystorePassword);

For PKCS #11 key stores a service name and password provider are used as follows:

ks.load(**this**);

where this implements the interface KeyStore.LoadStoreParameter. The only method to implement is the following:

@Override
public ProtectionParameter getProtectionParameter() {
  return new KeyStore.PasswordProtection(ivKeystorePassword);
}

After this, reading the key-store and doing cryptographic operations are the same for both types of key-store.

Listing Entries in a Key Store

Once the key store is loaded with the objects it should contain, and when access has been granted, its entries can be listed by calling the aliases() method as in the following example:

public void list(KeyStore keystore) throws ... {
  Enumeration<String> aliases = keystore.aliases();
  while (aliases.hasMoreElements()) {
    String a = aliases.nextElement();
    Entry k = keystore.getEntry(a, getProtectionParameter());
    String sName = k.getClass().getName();
    println("alias="+String.format("%1$-24s", a)+", type="+ sName.substring(sName.indexOf('$')+1));
  }
}

Reading and Using Key store Entries

When an alias is known, the entry can be read directly. Its use depends on the type of entry. The code looks like the following example:

public void readExample(String alias, KeyStore keystore) {
  Entry k=getEntry(alias,keystore);
  if(k instanceof PrivateKeyEntry){
    PrivateKey pk=((PrivateKeyEntry)k).getPrivateKey();
    if(pk.getAlgorithm().equals("DSA")){
      byte[]signature = ivLastSignature = 
               privateKeySign(pk, clearText, "SHA1withDSA");
    }
			
    Certificate[] chain=keystore.getCertificateChain(alias);
    // Certificates may need to be reordered
    PublicKey key=chain[chain.length-1].getPublicKey(); 
    //Example:publicKeyVerify(key,clearText,signature,algorithm);
  }else
  if(k instanceof TrustedCertificateEntry){
    Certificate c = ((TrustedCertificateEntry)k).getTrustedCertificate();
    PublicKeykey=c.getPublicKey();
    if(key.getAlgorithm().equals("DSA")){
      boolean verified = 
          publicKeyVerify(key, clearText, ivLastSignature, "SHA1withDSA");
    }			
  }else
  if(k instanceof SecretKeyEntry){
    SecretKey sk=((SecretKeyEntry)k).getSecretKey();
    if(sk.getAlgorithm().equals("AES")){
      byte[] encrypted = secretKeyEncrypt(sk, new byte[16], clearText, 0,
                     clearText.length, "AES/CBC/PKCS5Padding");
    }
  }
}

Encrypting with a Secret Key

The following example code implements a method which encrypts data and returns an array of encrypted bytes:

public byte [] secretKeyEncrypt(SecretKey sk, byte [] iv, byte [] input,int inputOffset, int inputLen, String transformation) {
  IvParameterSpec ivp = new IvParameterSpec(iv);
  // ivProvider as loaded above
  Cipher cipher = Cipher.getInstance(transformation, ivProvider);
  cipher.init(Cipher.ENCRYPT_MODE, sk, ivp);		
  return cipher.doFinal(input, inputOffset, inputLen);
}

Private Key Signing and Public Key Verifying Operations

The following example code creates a signature with a private key**:**

byte [] privateKeySign(PrivateKey pk, byte [] data, String algo) {
  Signature s = Signature.getInstance(algo, ivProvider);
  s.initSign(pk);
  s.update(data);
  return s.sign();
}

The following example code verifies a signature with a public key:

boolean publicKeyVerify(PublicKey pk, byte [] data, byte [] signature, String algo) {
  Signature s = Signature.getInstance(algo, ivProvider);
  s.initVerify(pk); // also has a method with a certificate
  s.update(data);
  return s.verify(signature);		
}

The examples are independent of provider. However, if the code does not use the provider parameter then the provider must be the first provider in the list, see Configuration of a PKCS #11 Provider.

The following example code shows to to use a default provider:

s = Signature.getInstance("SHA1withRSA"); // no provider, uses default provider

Generating Key Pairs

Key pairs can be generated with a KeyPairGenerator.

A key pair is generated by calling the generateKeyPair() method of an initialized KeyGenerator.

Examples of initializing a KeyGenerator as follows:

public KeyPair generateRSAKeyPair(int bitSize) {
  KeyPairGenerator generator = 
      KeyPairGenerator.getInstance( "RSA", ivProvider );
  AlgorithmParameterSpec params = 
      new RSAKeyGenParameterSpec(bitSize, RSAKeyGenParameterSpec.F4);
  generator.initialize(params);
  return generator.generateKeyPair();
}

// EC example:
KeyPairGenerator generator = getKeyPairGenerator("EC");
AlgorithmParameterSpec params = new ECGenParameterSpec("secp256r1");
generator.initialize(params);

// DSA example:
KeyPairGenerator generator = getKeyPairGenerator("DSA");
generator.initialize(bitSize);

Note: Keys generated with JCE will be session keys with PKCS#11.

The following algorithms are supported:

RSA 1024, 2048, 4096
EC "secp256r1"
DSA 1024

Generating Secret Keys

Secret keys are generated with a KeyGenerator (the same way as key pairs).

A secret key is generated by calling the generateKey() method of an initialized KeyGenerator.

public SecretKey generateSymKey(String algorithm, int keySize) {
  KeyGenerator generator = getKeyGenerator(algorithm);
  generator.init( keySize );
  return generator.generateKey();
}

JCE/pkcs11 supports the following algorithms and sizes:

DES 56 (64 bit but no parity)
DESede (Triple DES) 168
AES 128 and 256

For more details see: http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher

Creating Keys

A key that is known in a byte array can be converted into a SecretKey without having to go through a provider-based SecretKeyFactory or KeyGenerator as in the following example:

SecretKey k = new SecretKeySpec(CCAUtil.X2B("29fcbbc506989671909510f08c723f51de5836aa58a17cb7d098bdc5757fc0ce"), "AES");

Storing Keys

As mentioned previously, keys are created or generated as session keys. Keys are added to a key-store by calling the method setEntry() as in the following example:

public void addSecretKey(KeyStore ks, SecretKey k, String alias) {
  KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(k);
  ks.setEntry(alias, skEntry, null);
}

To add a PrivateKey is a bit more complicated as a certificate is required. How to create such certificate is out of scope of this document. See the following example of the concept:

Certificate [] certs= …;
KeyPair kp = …;
KeyStore ks = …;
KeyStore.PrivateKeyEntry pkEntry = new KeyStore.PrivateKeyEntry(kp.getPrivate(), certs);
ks.setEntry(alias, pkEntry, null);

Finally, when objects have been added to the key store, the key-store may be saved by calling its store() method:

ks.store(null);

Message Authentication Code

The key must be a generic secret key. Also the CKA_SIGN attribute must be true. These keys do not support encryption or decryption, they can be used in HMAC operations.

Example:

SecretKey k = …;
Mac hm = Mac.getInstance(algorithm, ivProvider);
hm.init(k);
mac = hm.doFinal(clearText);

For more information, see:

http://docs.oracle.com/javase/8/docs/api/javax/crypto/Mac.html

For supported algorithms, see “Provider Services and Algorithms” below.

CBC-MAC

PKCS#11 has MAC algorithms for AES and DES, and these are not supported through JCE/PKCS11. The MAC can be calculated by a CBC operation as in the following example:

public byte [] secretKeyMac(SecretKey sk, byte [] iv, byte [] message) {
byte [] mesPadded = zeroPad0(message, iv.length);
AlgorithmParameterSpec ivAcps = new IvParameterSpec(iv);

  byte [] mac = secretKeyEncrypt( sk, iv, mesPadded, 0, 
             mesPadded.length, "AES/CBC/NoPadding"); // or DES
  // return the last half block
  return Arrays.copyOfRange(mac, mac.length-iv.length, 
             mac.length-iv.length/2); 
}

Provider Services and Algorithms

A provider will provide a list of services. These can be listed as in the following example:

Provider provider = ...
Set<Provider.Service> s = provider.getServices();

for (Service x:s) {
  StringBuffer sb = new StringBuffer();
  sb.append("Service type="+x.getType()+", algorithm="+x.getAlgorithm());
  println(sb.toString());	
}

Sorted output for Provider=sun.security.pkcs11.SunPKCS11, name=SunPKCS11-CCCPKCS11 as listed in the table below:

Service type Algorithm
AlgorithmParameters EC
Cipher AES_128/CBC/NoPadding
Cipher AES_128/ECB/NoPadding
Cipher AES_192/CBC/NoPadding
Cipher AES_192/ECB/NoPadding
Cipher AES_256/CBC/NoPadding
Cipher AES_256/ECB/NoPadding
Cipher AES/CBC/NoPadding
Cipher AES/CBC/PKCS5Padding
Cipher AES/ECB/NoPadding
Cipher AES/ECB/PKCS5Padding
Cipher DES/CBC/NoPadding
Cipher DES/CBC/PKCS5Padding
Cipher DES/ECB/NoPadding
Cipher DES/ECB/PKCS5Padding
Cipher DESede/CBC/NoPadding
Cipher DESede/CBC/PKCS5Padding
Cipher DESede/ECB/NoPadding
Cipher DESede/ECB/PKCS5Padding
Cipher RSA/ECB/NoPadding
Cipher RSA/ECB/PKCS1Padding
KeyFactory DSA
KeyFactory EC
KeyFactory RSA
KeyGenerator AES
KeyGenerator DES
KeyGenerator DESede
KeyPairGenerator DSA
KeyPairGenerator EC
KeyPairGenerator RSA
KeyStore PKCS11
Mac HmacSHA1
Mac HmacSHA256
Mac HmacSHA512
MessageDigest SHA1
MessageDigest SHA-256
SecretKeyFactory AES
SecretKeyFactory DES
SecretKeyFactory DESede
Signature DSA
Signature MD2withRSA
Signature MD5withRSA
Signature NONEwithECDSA
Signature RawDSA
Signature SHA1withECDSA
Signature SHA1withRSA
Signature SHA224withECDSA
Signature SHA224withRSA
Signature SHA256withECDSA
Signature SHA256withRSA
Signature SHA384withECDSA
Signature SHA384withRSA
Signature SHA512withECDSA
Signature SHA512withRSA

Use Java keytool to Generate and List Objects

keytool is a command line utility for operating key-stores. Key-stores can be both java key-stores (JKS) and PKCS#11 key-stores.

On Linux we can use a small shell script like in the following example:

#
# Uses Java keytool to generate keys and certificates
# Example: ktool.sh -list
#
# Find out where the configuration file is...
if [ -d test ]; then
PROJ=$PWD
else
PROJ=$PWD/..
fi

## Check absolute path in ep11.cnf
CNF=$PROJ/config/ep11.cnf
PRVCLS=sun.security.pkcs11.SunPKCS11
#Note! The following is for IBM JRE provider
#PRVCLS=com.ibm.crypto.pkcs11impl.provider.IBMPKCS11Impl
keytool -storetype pkcs11 -storepass zaqwsx -providerclass $PRVCLS -providerarg $CNF "$@"

Keytool uses the same configuration file as above; see Configuration of a PKCS #11 Provider.

Storepass is not used when using the CC ACSP client; but it must be specified.

Now we can use this script to list the content using the following command:

$ ./ktool.sk -list

and to generate an EC key pair as in the following example:

$ algo=EC
$ keysize=256
$ label=testlabel
$ ./ktool.sh -genkeypair -keyalg $algo -keysize $keysize -alias $label -validity 3660 -dname "CN=${label} ACSP, OU=CCCC, O=IBM Denmark, L=Copenhagen, ST=Unknown, C=dk"

For options and limitations see the keytool documentation

Using PKCS#11 Key Stores as TLS Trust store and Key store

It is possible to setup SSL/TLS to use a PKCS11 trust store and key store, see also the PKCS#11 guide referenced in the above link.

The trust-store can be configured with Java VM arguments:

-Djavax.net.ssl.trustStoreType=PKCS11 -Djavax.net.ssl.trustStore=NONE

The key-store can be configured with Java VM arguments like in the following example:

-Djavax.net.ssl.keyStoreType=PKCS11 -Djavax.net.ssl.keyStore=NONE

For programs that cannot load the pkcs11 provider dynamically the pkcs11 provider should be added to the JRE provider list. See Configuration of a PKCS #11 Provider.

Note: The same pkcs11 key store is used for trust and private keys.

Differences between Oracle and IBM Java

Oracle Java IBM Java
Name of Service type Key store PKCS11 PKCS11IMPLKS
Name of Generic Secret Key Algorithm “Generic Secret” “Generic”
RSA key generation Supported Not supported
Using PKCS#11 key stores as TLS trust store and TLS key store Supported Not supported

CC ACSP User Defined Functions

The CC ACSP User Defined Functions (UDF) feature has one primary goal – the ability to perform several calls to CCA functions as one transaction to the server. This will minimize network traffic and give better response times.

The UDF will also open for execution of User Defined Extensions (UDX) execution on the CC ACSP server, to be invoked from the client side. UDX functions can be executed on the server side in a UDFCALL function which is wrapped into a normal Java class method. For UDXCALL see also jCCA Generic UDX Verb.

CC ACSP provides support for UDFs through the jCCA defined pseudo-verb UDFCALL:

  • CCA type calls: Supported by jCCA and CC ACSP
  • The CC ACSP server UDF functions do not have to be static.
  • Support for CCAExceptions
  • Supported by the CC ACSP CCA C Client and the CC ACSP Java Client.
  • Retry and failover support.

UDF overview

The CC ACSP solution supports development and deployment of customized functions through the jCCA framework. JCCA is a Java interface to CCA used in the CC ACSP solution. It allows users to request crypto operations from CCA verbs in the Java domain (see jCCA Programming). A customized function is known as a User Defined Function (UDF) and provides utility functions through the CC ACSP defined UDF API (AUdfFunction).

A UDF constitute a server-side program deployed on a CC ACSP server. It is called via UDFCALL verb from a client-side program. The client-side program requests customized crypto operations from the UDF. The UDF code typically contains multiple calls to jCCA implemented CCA verbs. Thus, the UDF process the client’s request and carry out the respective calls to the CCA verbs. This setup implies that users can make one call to the server, from which they obtain multiple calls to CCA verbs, and as a result either optimize their crypto applications and/or simplify a process.

UDF calling sequence for a deployed and local setup

Beginning with release 2.1.4 of the CC ACSP developer package it is possible to make local UDF executions. This means a UDF can be tested locally in a developer environment before being deployed to a CC ACSP server for production. Figure 4.1 illustrates both the deployed and local setup of a CC ACSP UDF. In both setups, a call to the UDF is going through UDFCALL.

For a deployed UDF the transaction is sent to the server for execution, as oppose to a local execution, where the UDF is executed on the client-side by the UDF executor. In both cases the CCA verbs called are executed on the server side, but when local executor is used, the CCA verbs is sent to the server for execution via the CC ACSP client. This means a local setup makes more transactions to the server, but is exempted from the deployment process to a CC ACSP server. Either way the result is identical.

Deploying a UDF for production is described in UDF deployment.

Calling a UDF from the CC ACSP Java Client

Whether a UDF is configured for local execution or deployed to a CC ACSP server, the call to the UDF from a client program is the same. This means the UDF server code and most of the client code can stay unchanged, when the function is ready for production. Only one line of code on the client-side determines if UDF execution is local, and hence the only line to remove from production code.

A UDF is called from a client via the UDFCALL verb, which can be imported into the java client program with:

import com.ibm.crypto.hardware.generic.UDFCALL

UDFCALL can be called like any other jCCA implemented CCA verb, for example:

UDFCALL call = new UDFCALL();
try {
    call.setFunction(full name of UDF class);
    call.addRules(…);
    call.setDataIn(…);
    call.setDataOut(…):
    call.execute():
catch (CCAException e) {
    …
}

The UDFCALL class implements setter and getter methods for users to configure the call to the UDF. For example to:

  • set the UDF function name to be executed
  • set the rules for the UDF function to evaluate
  • UDF specific input data
  • UDF output array return data

Once the fields of UDFCALL are set, it is ready for execution. This is done by calling the method execute(). See UDF development for an elaborated description on UDF development.

UDFCALL setter methods

  • setFunction: used to set the UDF function name to be executed. This should be the fully qualified class path name of the function. For example, for the provided sample program UdfMac that would be “acsp.samples.UdfMac”.
  • (addRules: sets the rule field of UDFCALL inherited by the UDF).
  • setDataIn:Sets the data input field. Usually data required for a CCA verb. For example a key label.
  • setDataOut: Sets the data output field. Usually the return value from the UDF.
  • setDataOutLength: Sets the data output length for output allocation.
  • SetRR: Sets the return/reason codes of UDFCALL.

UDFCALL getter methods

  • getDataIn: Retrieve the data input field which has been set for the UDF call. Returns a zero length byte array if not set.
  • getDataInLength: Retrieve the data input field length. Returns a length of zero if not set.
  • getDataOut: Retrieve the data output field which has been set for the UDF call. When called before execution a zero length byte array will be returned and when called after execution the output returned from the UDF function will be returned if the function has succeeded.
  • getDataOutputLength: Retrieve the data output field length. Returns a length of zero if not set.
  • getFunction: Retrieve the function name which has been set of the UDF call. Returns function name or empty string if not set.
  • getFunctionLength: Retrieve the function field length. Returns a length of zero if not set.
  • getRR: Get the return/reason code pair of UDFCALL.

In summary calling a UDF is the same for both a local and deployed UDF. It requires an instance of UDFCALL on the client-side which must be set to the UDF fully qualified class path name.

UDF development

We call a UDF from a client-side program via the jCCA verb UDFCALL. This implies we need to write code for the client side and code for the server side (the latter resulting in the UDF).

On the client-side we have to:

  • Instantiate UDFCALL
  • Set the fields of a UDFCALL instance:

SetFunction to set the fully qualified class path name of the UDF*, addRues* to set the rulesfor UDF control logic*,* and setDataIn to pass data if the jCCA verb requested in the UDF requires input data.

  • Call the UDF by calling the execute method on the configured UDFCALL instance*.*
  • Allocate for and receive return values from the UDFCALL by setDataOutLength and getDataOut.
  • Handle possible CCAException inherited from the UDF and jCCA implemented CCA verbs.

The client-side program can print test data by extending the UtilMethods class as well as importing the CCAUtil package:

import com.ibm.crypto.utility.UtilMethods;
import com.ibm.crypto.hardware.common.CCAUtil;

See the CallUdfMac Sample Client Code on the various test methods.

A UDF must extend the CC ACSP defined AUdfFunction class. Hence, in the UDF we have to:

  • Import and inherit (extend) the AUdfFunction class that provides the CC ACSP UdfFunction API:
import com.ibm.acsp.udf.AUdfFunction;
  • Override the execute method of the AUdfFunction API:
    At runtime the method is overridden, and hence the first method of the UDF to be executed.
  • Query for passed rules in the execute method by using the queryRules() or getRules() methods:
    The queryRules method gets the value of the passed rules from UDFCALL by matching its input string. Use this to control which crypto operations the UDF should carry out. Alternatively, the getRules method can be used to index the array as shown below.
getRules()[0];
queryRules(“GENKEY”)
  • Get data passed from client:
    Some CCA verbs require input data and in these cases setDataIn was called on the UDFCALL instance on the client side. When evaluated to the UDF at runtime, the data is retrieved by getDataIn. If more than one input data type is needed, TLV packed data can be used to separate those.

  • Return and set return/reason codes or throw CCAException with proper UDF defined return/reason codes*:
    Some functions may return only status codes, but in other cases the requested data can be returned. The method setDataOut is used to return output data to the client. If more than one output data type is needed, TLV packed data can be used to separate those.

See Sample program walk-through: CallUdfMac and UdfMac for an elaborate walk-through of developing a UDF and it’s respective calling program.

UDF development for local execution

As of release 2.1.4 of the CC ACSP developer package, a UDF can be configured to execute locally. Executing a UDF locally means the UDFCALL defined function is executed in a local developer environment as oppose to executing on the CC ACSP server. This implies the developer environment to support jCCA natively or via the CC ACSP client. In this manual we show how to execute a UDF locally via the CC ACSP client. It requires two steps:

  • Configure the CC ACSP client as jCCA provider
  • Write code to enable local execution

Configuring the CC ACSP client as jCCA provider

As with normal execution we configure the CC ACSP Java Client as the jCCA provider. The jcca.properties file configures jCCA as described in Using CC ACSP client with jCCA. To setup the CC ACSP client as provider of jCCA we set the parameter cca-root-class to the CC ACSP client as shown in the following statement:

cca-root-class=com.ibm.acsp.client.protocol.cca.ProtocolCcaClient

Note: This is the default setup in the CC ACSP developer package.

Write code to enable local execution

To tell jCCA to execute the UDF function locally we have to call a method in the static jCCA class CCACommon which has to be imported with:

import com.ibm.crypto.hardware.common.CCACommon;

To enable local execution, we have to pass an instance of the UdfCallExecutor which is provided by the CC ACSP client. This is imported with:

import com.ibm.acsp.client.udf.UdfCallExecutor;

The CCACommon method setUdfExecutor call looks as follows:

CCACommon.setUdfExecutor(new UdfCallExecutor());

With this setup the UDFCALL function will be executed locally, and the need for deploying the UDF to a CC ACSP server is removed. Now UDFCALL can be instantiated as described Calling a UDF from the CC ACSP Java Client.

The only difference on local UDF execution contra normal execution, is that one line of code on the client-side program, where the UDF executor utility class is instantiated. Remove that one line of code from the client-side program to execute normally on a CC ACSP server.

UDF deployment

The final UDF code must be deployed to the server before it can be used for production. After deployment it can be called from the CC ACSP client via a UDFCALL client call.

The UDF classes must be accessible as read only by the running CC ACSP server process.

Note: If the UDF client call program have previously instantiated a local execution as described in UDF development for local execution, this line of code must be removed from production code.

Naming the UDF Function Class

The UDF package and class should be named uniquely overlap any CC ACSP provided packages. In general this means that customized UDFs must not not start with:

*com.ibm.acsp...*which are the name space of CC ACSP.

Typically the reverse name of the internet domain name for a company are used. Examples:

  • A company with domain name kyndryl.com could name a UDF as com.kyndryl.finance.Procedure1
  • A company with domain name landing.co.uk could name a UDF as uk.co.landing.EncryptAll

Adding a New UDF Library to the CC ACSP Server

Please carry out the following steps to add a new Java library to the CC ACSP server:

  • Create a jar file with the compiled Java code, for example by using Maven, Ant or Eclipse.
  • Copy the jar file to the ./lib directory of the CC ACSP server
  • Restart the CC ACSP server (stop/start)

Updating a UDF Library to the CC ACSP Server

Please do the following 3 steps to update an existing Java library to the CC ACSP server:

  • Copy the updated jar file to the ./lib directory of the CC ACSP server, replacing the old one.
  • Restart the CC ACSP server (stop/start)

Updating the CC ACSP server Authorization for UDF Execution

The authorization resources for the UDF class must be defined and authorized if the authorization has been enabled. If the authorization feature is not enabled, this step can be omitted.

The deployed UDF class and method must be defined and authorized in the Authorization System using a named UDF resource with the following syntax:

acsp.udfcall.<package-name>.<class-name>

The resource name must be defined in the authorization system, and the client user ID or client group ID must be given permission to the resource name with at least read access.

AUdfFunction Inherited Functions

The following methods are available in the CC ACSP UDF programming framework:

  • boolean checkKeyLabelAccess(label) Check for access to the key label in the CC ACSP resource access system. Return true id access allowed or false otherwise.
  • void execute() Execute the UDFCALL function
  • byte[] getDataIn() Retrieve the data input field which has been set for the generic UDF call. This returns a zero length byte array if not set.
  • byte[] getDataOut() Retrieve the data output field which has been set for the generic UDF call. this returns a zero length byte array if not set.
  • UDFCALL getInstance(); Get the instance of the UDFCALL jCCA object, to allow access to the getters and setters. This can for example be used to set or reset the return and reason codes for the verb.
  • String getKeyPrefix() Get the key prefix which has been set up for the client. If the prefix has not been set, an empty string is returned.
  • Properties getProperties() Get the Properties of the UDF.
  • String getProperty(String key) Get property value from the UDF properties file or null if not set.
  • String getProperty(String key, String default) Get property value from the configuration file or default if not set.
  • String[] getRules() Get the rules specified to UDF as a string array.
  • String getUserID() Get the Client user ID, which has called this function. If the user ID has not been set, an empty string is returned.
  • boolean hasKeyPrefix() Return true if prefix has been specified
  • void logDebug(String message,Object ... args) Log UDF Debug messages in the Log4J log
  • void logError(String message,Object ... args) Log UDF Error messages in the Log4J log
  • void logInfo(String message,Object ... args) Log UDF Info messages in the Log4J log
  • void logTrace(String message,Object ... args) Log UDF Trace messages in the Log4J log
  • void logWarn(String message,Object ... args) Log UDF Warning messages in the Log4J log
  • boolean queryRule(String rule) Query if a rule is specified.
  • void saveProperties() Save the properties, if they have been updated.
  • void setDataIn(byte[] dataIn) Set the data input field for the generic UDF call.
  • void setDataOut(byte[] dataOut) Set the data output field for the generic UDF call.
  • void setInstance(UDFCALL) Sets the instance of the UDF.
  • void setKeyPrefix(String) Set the key prefix which has to be prepended to key labels.
  • String setProperty(String key, String value) Set the UDF property value of the key. If previous set, return the value of .void setRules(String[] rules) Set the rules for the UDF function.
  • void setUserID(String) Sets the authorization user id, which has called this function.

Sample program walk-through: CallUdfMac and UdfMac

In this section we will go through the provided sample programs CallUdfMac (client-side code) and UdfMac (server-side code) to demonstrate how a UDF is build.

We start by writing the client-side code. Here we need to import the UDFCALL class. As with other classes within jCCA the class name can be shortened using the Java import statement in the top of the Java source code for the class as in the following statement:

import com.ibm.crypto.hardware.generic.UDFCALL;

Parameters to UDFCALL can be set indirectly by using both the jCCA provided structure known as a Tag-Length-Value (TLV) and the verb’s setter methods. The TLV approach is a simple way of passing rules and data between the client and server code that constitutes a UDF. The CallUdfMac client program encapsulates UDFCALL in an execute method. The method takes two inputs: a byte array and a string array, then returns a list of TLV elements:

private static TLVContainer.ElementList execute(byte[] data, String ... operations) throws CCAException {
.
.
.
}

These input parameters are further passed as arguments to the UDFCALL’s setter methods. To do so, however, we must first instantiate an object of UDFCALL and set the corresponding UDF class that we want to call. For this we use setFunction:

private static TLVContainer.ElementList execute(byte[] data, String ... operations) throws CCAException {
	       UDFCALL call = new UDFCALL();       			 				call.setFunction(“acsp.samples.UdfMac”);
		.
		.
}

The remaining parameters, data and operations (i.e. rules), to the UDFCALL instance are read from input parameters of the execute method. We set the operations to UDFCALL by its method addRules:

call.addRules(operations);

Note: The operations parameter denotes the rule(s), which our UdfMac program will use to switch on, and hence to further request a crypto operation. Whereas the data parameter denotes the data a CCA verb might need. For example a key label.

We can add data to UDFCALL if required by the function. We use setDataIn on the UDFCALL instance to do so:

if (data!=null) {
  call.setDataIn(data);
}

Next, we need to specify the data output length. The setDataOutLength size is defined by the user, and it should be equal to or higher than the length of the response returned by the server-side program, otherwise an exception will be thrown.

Once set, we are ready to execute the UDFCALL, which will call our server-side program:

call.setDataOutLength(4096);
call.execute();

Data is returned and read from the UDFCALL instance by using the getter method getDataOut. In terms of using the TLV structure we can format this output as elements decoded by the TLVContainer class as shown in the following statement:

return TLVContainer.decode(call.getDataOut());

Now we can use the execute method inside our CallUdfMac (client-side) class to call the UdfMac (server-side) class. The complete execute method then looks like the following:

private static TLVContainer.ElementList execute(byte[] data, String ... operations) throws CCAException {
    UDFCALL call = new UDFCALL();
    call.setFunction(UDFCLASS);
    call.addRules(operations);
    if (data!=null) { 
        call.setDataIn(data);
    }
    call.setDataOutLength(4096);
    call.execute();
    return TLVContainer.decode(call.getDataOut());
}

We continue to write methods for the CallUdfMac class. One of which will request a Message Authentication Code (MAC) on the inputted data from a given key label. This will be called generateMAC. It will use the TLVContainer class provided by jCCA to pass our data to the above execute method. Hence, we instantiate a container inside the generateMAC method as follows:

TLVContainer con = new TLVContainer();

We want to obtain the generated MAC, thus we allocate a byte array for it to return. Further we will provide a key label and the data to associate the MAC with as input. The method then looks like following:

public byte[] generateMAC(CCAKeyLabel keylabel,byte[] macData) {
    TLVContainer con = new TLVContainer();
    byte[] mac = new byte[0];
    .
    .
}

Now we can pass the parameters to the container instance by using its addElement method as such:

con.addElement(keylabel);
con.addElement(macData);

And finally we call the UDF using the previous defined execute method:

TLVContainer.ElementList res = execute(con.encode(), "MACGEN");

Note that the container is passed as a byte array by calling its encode method on the container instance. Moreover, recall the execute method returns a list of elements encoded by the TLVContainer class. To get the values from the list, we can apply the getter methods on the returned object by indexing the list as followed:

mac = res.getBytes(0);

Since the execute method carries out the call to UDFCALL it also throws CCAExceptions inherited from CCA verbs called in the UdfMac server-side code. These exceptions must be handled from our calling code. We enclose the call to the UDF inside a try-catch block, and as such the complete calling code for generateMAC looks like the following:

public byte[] generateMAC(CCAKeyLabel keylabel,byte[] macData) {
    TLVContainer con = new TLVContainer();
    byte[] mac = new byte[0];
		
    try {
        con.addElement(keylabel);
        con.addElement(macData);
        TLVContainer.ElementList res = execute(con.encode(), "MACGEN");
        mac = res.getBytes(0);
        say("%s: Generated MAC value = %s%n",
            this.getClass().getName(),CCAUtil.B2X(mac));
    } catch(CCAException e) {
        say("%s: %s",this.getClass().getName(),e);
    }
    return mac;
} 

Note: When calling a UDF deployed to a CC ACSP server the security administrator of the CC ACSP server has to authorize the client to execute the UDF function class, otherwise a CCAException will be thrown with a return code 32 and reason code 90.

Please see CallUdfMac Sample Client Code for a description of the full client-side source code.

Next we write the server-side code UdfMac. UdfMac overrides the execute method, which is called as the first method when UDFCALL executes. In this method we will switch on the passed rules from the UDFCALL instance. Recall this was the operations parameter. These are retrieved by calling the getRules or queryRule methods as in the following example:

getRules()[0];
queryRule(“MACGEN”);

Furthermore, to get the passed data, we use the TLV construction by declaring a private member to the UDF class as an ElementList of the TLVContainer class:

private static TLVContainer.ElementList inArgs;

All methods of the UDF class use this variable to read passed data by indexing the list as shown in the following:

CCAKeyLabel keylabel = inArgs.getCCAKeyLabel(0);
byte[] macData = inArgs.getBytes(1);

Recall the key label was set first on the client-side code, followed by the data to be associated with the requested MAC. Hence, on the server-side code we must retrieve the data in this sequence too.

If the UDF needs to return data to the client, we can use the TLVContainer class to pack this like we did on the client-side code:

TLVContainer outArgs = new TLVContainer();
outArgs.addElement(mgn.getMAC());

The result is formatted as a byte array by the encode method of the TLVContainer class, which can be returned by calling the setDataOut method as in the following example:

setDataOut(outArgs.encode());

The UdfMac sample defines two more methods for the MAC operations. We will not go through them all here. The idea is, however, that each rule passed on the UDFCALL instance, is switched out in the server-side code to further call the respective method. In the UdfMac sample that looks like the following:

if(queryRule("GENKEY")) {
	generateMacKey();
} else
if(queryRule("MACGEN")) {			
	generateMac();
} else
if(queryRule("MACVER")) {
	verifyMac();
}

The complete description of the UdfMac server-side code can be found at UdfMac Sample UDF Server Code.

Sample program walk-through: AgileClient and AgileUDF

In this section we will walk through AgileClient (client-side code) and AgileUDF (server-side code) programs to demonstrate the crypto agility offered by UDF and its properties. Cryptographic agility is about an information security system rapidly switching to alternative cryptographic primitives and algorithms without making significant changes to the system’s infrastructure, a core component of cyber resiliency. If a cryptographic algorithm is found to no longer be secure or does not meet the regulation requirements, the ability to switch to a secure algorithm quickly is essential. Using AgileUDF function makes a business application crypto-agile because the cryptographic policy is applied at the CC ACSP server level, which ensures that the correct algorithm is used within a crypto operation. Such crypto operations that are provided by the AgileUDF are:

  • key generation/deletion/existence check
  • signing data/verifying signed data

UDF properties are used to persist multiple encryption schemes and their settings as well as provide a mechanism of switching between them independently to sign and verify signed data. This allows not only to avoid transmitting the encryption scheme settings on each request from the client to the server, but also enforce the cryptographic policies at a single point on the server side where each encryption scheme is referenced using the use value variable property. Some of the use-case examples include:

  • When a crypto algorithm became insecure, rapidly switch to another
  • Successfully verify RSA key signed data after UDF is set to use QSA scheme
  • Switching between different keys when signing and verifying data

Here is an example of how a property set storing settings for a single encryption scheme looks like:

dsa.gen.parms = ecc,1,256
dsa.gen.rules = ECC-PAIR,SIG-ONLY
dsa.hash.rules = SHA-256
dsa.key = ACSP.AGILEUDF.DSA
dsa.sign.rules = ECDSA,MESSAGE,SHA-256
dsa.verify.rules = ECDSA,MESSAGE,SHA-256

Note how the property keys are prefixed with the use value of an encryption scheme which these properties are used for.

We start observing the code from the client-side, AgileClient which is a command line tool that is designed to serve commands in an interactive fashion where it reads a user command and executes the corresponding AgileUDF operation.

Within the client-side code we work with the UDFCALL object in exactly the same way as in our previous sample except the setFunction call where we specify the UDF class that we want to call on the server side.

private static final String UDFCLASS_SERVER = "com.ibm.acsp.server.udf.AgileUDF";
.
.
.
private static TLVContainer.ElementList execute(byte[] data, String ... operations) throws CCAException {
    UDFCALL call = new UDFCALL();

    call.setFunction(UDFCLASS);
    call.addRules(operations);
    if (data!=null) {
        call.setDataIn(data);
    }
    call.setDataOutLength(4096);	// size enough
    call.execute();
    return TLVContainer.decode(call.getDataOut());
}

Now next thing we do is initialization of the AgileUDF properties by sending the relevant operation without any input parameters to the UDF.

execute(null,"SETUP");

This piece of code is executed when a user enters the interactive command into the console:

prop init

At this point the UDF will produce 3 property sets for encryption schemes including dsa, rsa, qsa and set them up with default rule values.

Besides the initialization of the properties the UDF also provides means to manage them.

listProperties will output the key-value pairs of all the UDF properties into the ElementList nested class of the TLVContainer:

private void listProperties() {
    TLVContainer.ElementList res = null;
    try {
		res = execute(null,"LISTPROP");

To get a single property value getProp(String key) is used where we provide the key of the property, e.g. 'rsa.key':

con.addElement(key);
.
.
.
res = *execute*(con.encode(),"GETPROP");

Likewise, to change a value of a property, setProp(String key,String value) can be called with a key string of the property along with a new value. In case the new value is empty, the property itself will be deleted.

private void setProp(String key,String value){
    TLVContainer con = new TLVContainer();
    con.addElement(key);
    con.addElement(value);
.
.
.
    execute(con.encode(),"SETPROP");

Lastly to copy the values of a whole property set copyProp(String from,String to) can be called. It accepts two parameters - the source use value to copy the property set values from and the target use value to copy the property set values to.

private void copyProp(String from,String to){
    TLVContainer con = new TLVContainer();
    con.addElement(from);
    con.addElement(to);

    say("Copying property set '%s.*' to property set '%s.*'%n",from,to);
    	
    try {
        execute(con.encode(),"COPYPROP");
    } catch (CCAException e) {
        String msg = e.toString();
        if (e.isRR(32, 1833)) {
            msg = String.format("from use set(%s) is not found",from);
        } else
        if (e.isRR(32, 1835)) {
            msg = String.format("to use set(%s) is not found",to);
        }
        say("COPYPROP function failed with: %s%n",msg);
    }
}

Thus it is possible to add a new encryption scheme, e.g. ecc by running the interactive command:

prop copy dsa ecc

After the new set of properties is generated, we update the key label for the new encryption scheme:

prop set ecc.key ACSP.AGILEUDF.ECC

Then update the use value selection property:

prop set use.values dsa,rsa,qsa,ecc

Lastly, key generation, digital signature and verification rules are updated accordingly.

Note, how the return code 32 and the reason codes 1833, 1835 are used to handle cases when one of the sets (from or to) could not be found.

Then we can check if the PKA key itself is present for an encryption scheme with the help of checkKeys method.

private void checkKeys() {
    TLVContainer.ElementList res = null;
    try {
        res = execute(null,"CHECK");

If we get 'key not found' response for any of those, we can go ahead and generate one.

execute(con.encode(),"GENKEY");

Here we pass a container where we place the use value of the encryption scheme to generate the PKA key for, e.g. ‘qsa’.

Note that upon running the checkKeys again it will now produce 'key present' response.

Now we can proceed with our request to sign text by calling the sign method. To do this we will turn the text into a byte array, place it inside the TLVContainer and encode before sending to the server-side UDF.

res = *execute*(con.encode(),"SIGN");
signature = res.getBytes(0);
with = res.getString(1);

Signature is returned along with the key use value as a result of the executed method.

To verify the text that was signed verify method is used which accepts a container with the encryption scheme use value along with the text and the signature.

con.addElement(data);
con.addElement(signature);
con.addElement(meta);
.
.
.
res = *execute*(con.encode(),"VERIFY");

Then we can instruct the UDF to switch the encryption scheme by providing the use value of the encryption scheme, e.g 'qsa':

execute(con.encode(),"USEKEY");

Finally we can delete a PKA key for an encryption scheme. Similarly here we also need to provide the use value of the encryption scheme.

execute(con.encode(),"DELKEY");

Please see AgileClient Sample Client Code a description of the full client-side source code.

Moving down to the server-side code - AgileUDF overrides the execute method where we query the rules that were passed from the client-side.

The code starts with declared user defined reason codes which are used when throwing CCAExceptions and are supposed to be handled on the client-side.

private static final int ERROR_UDF_AGILE_KEY_EXISTS = 1831;

Note how two main crypto methods, sign and verify, can work with either an explicitly provided use value of an encryption scheme or can pick one that is set to be used via the UDF properties:

String use = args.getString(1);

String use = args.getString(1);

if (use==null) {
    use = getProperty("use");
}

When verifying signed data this allows for a client-side to explicitly tell the AgileUDF that a particular data was signed using some specific encryption scheme that may be different from the one that is currently set to be used within the UDF properties.

Another side-note is that both these methods might do the crypto operation on the data itself or its hash:

if (queryRule("MESSAGE")) {
     dsg.setData(data);
} else {
    byte[] hash = hash(data, getPropertyRules(use + ".hash.rules"));
    dsg.setData(hash);
}

AgileUDF setProperty method allows not only to change a value of a property, but to remove it as well when passing an empty string.

private void setProperty(TLVContainer.ElementList args) throws CCAException {
    String key = args.getString(0);
    String value = args.getString(1);
    String ovalue = getProperty(key);
    	
    if (value.isEmpty()) {
    	getProperties().remove(key);
    	logInfo("remove property key(%s)",key);
    } else {
    	logInfo("Setting property key(%s) to value(%s) - old value(%s)",key,value,ovalue);
        setProperty(key, value);
    }
    	
    saveUDFProperties();
}

Another thing to note here is that the inherited saveProperties method needs to be called when doing any changes to the UDF properties.

Here are some details on how the AgileUDF performs the supported crypto operations.

When generating a key AgileUDF performs the following actions:

  • Build PKA key skeleton token by executing CSNDPKB JCCA verb supplying it with the rules extracted from the .gen.rules property value and parameters extracted from the .gen.parms property value
  • Pass the created skeleton token to CSNDPKG CCA verb with ‘MASTER’ rule to generate a key pair
  • Store the generated key inside the storage mechanism with the help of CSNDKRC CCA verb

When signing data AgileUDF will:

  • Depending on the rules passed into the UDF – either SIGN or SIGN MESSAGE, sign the hash of the message or the message itself using the CSNDDSG CCA verb and .sign.rules.The hash of the message is produced by executing CSNBOWH CCA verb and supplying it with rules extracted from the.hash.rules property value

When verifying signed data AgileUDF will:

  • Depending on the rules passed into the UDF – either VERIFY or VERIFY MESSAGE, verify the hash of the message or the message itself using CSNDDSV CCA verb and .verify.rules*.* The hash of the message is produced by executing CSNBOWH CCA verb and supplying it with rules extracted from the***.hash.rules***property value

Some of the UDF properties need further clarification, such as:

.gen.parms

where values are expected to be provided in the form:

string_param,int_param1,int_param2

Some of the possible values are:

ecc,1,256
rsa,2048,65537
qsa,1,2

where the string_param provides context for the AgileUDF about the key skeleton generation and the int_param1 and int_param2 are used in context of each type of key. Below table supported parameter correlation:

string_param int_param1 int_param2
Possible values Meaning Possible values Description Meaning Possible values Description
ecc Curve type 0 Prime curve Size of p in bits 192, 224, 256, 384 or 521 for Prime curves
1 Brainpool curve 160, 192, 224, 256, 320, 384, or 512 for Brainpool curves
2 Edwards curve 255 or 448 for Edwards curves
3 Koblitz curve 256 for Koblitz curves
rsa Modulus length in bits 512-2048 public exponent length in bits 0 Full random exponent
3 FERMAT-0
65537 FERMAT-4
qsa ‍ ‍ ‍ Algorithm identifier 1 CRYSTALS-Dilithium 0607 Clear key format 0 No clear key
1 Clear private and public key pair in KAT format (Known Answer Test)
2 CRYSTALS-Dilithium 0807 (In JCCA v. 1.6.7) 2 Clear private and public key pair in Parametrized format
3 Clear pub key with QSA-PUBL rule

The complete description of the AgileUDF server-side code can be found at AgileUDF Sample UDF Server Code.

Client Control and Host Reservation

The extended API for host reservation gives the CC ACSP client application the ability to control which host the calls are sent to. The hosts are those defined in the CC ACSP client configuration file. Host reservations are made per thread. This allows two threads to have different reservations, and it also allows all threads which have no reservations to continue to use all available servers.

After a thread has made a host reservation, subsequent CCA calls that are made by that thread, are sent to the reserved host. This ends when the thread releases its reservation.

API Functions

The following topics describe the host reservation functions, which can be used by an application to reserve and release host reservations, as well as querying configured hosts and the reason for any error, in case one should occur.

Client Control and Host Reservation Java package name and classes

The Host Reservation class is called: com.ibm.acsp.ClientController

Note! The old ACSP 1.5 class called ACSPClientController has been removed, the ClientController should be used instead.

All functions in the client control and host reservation API is accessed through this class in the CC ACSP Java Client.

The ClientController class is a singleton instance which is accessed by using its public static getInstance method.

Get Host List

Synopsis

public String[] getHostList()

Description

The getHostList function is used to query the CC ACSP Java Client for the list of available hosts. This can be used to iterate over each available host and perform a specific sequence of CCA calls on each host.

The host list is generated based on the client.connect and client.backup parameters.

Each parameter is a list of hosts and services. The host addresses are resolved as defined by the host definition.

Return value

Returns an array of strings, representing each available host.

Set Host Reservation

Synopsis

public boolean setHostReservation(String hostname)

Description

The setHostReservation function is used to reserve the use of a specific host for subsequent CCA calls. The reservation is specific to the thread, and does not affect the execution of calls which are made by other threads.

The provided hostname is validated against the hosts specified in the configuration file. If the provided hostname is not found, the reservation fails. The validation of the hostname is string comparison which is not case sensitive. This means that if a domain-name is specified in the configuration file, the corresponding IP address will not be accepted.

Call the releaseHostReservation function to end the reservation.

Note that a host reservation cannot be made, when another reservation is in effect. Attempting to do so, will cause the reservation to fail.

Return Value

On successful completion, this function returns true. If the function fails, it returns false.

Get Host Reservation

Synopsis

public String getHostReservation()

Description

The getHostReservation function is used to retrieve the host name of the host that was previously reserved.

Return Value

The hostname of the reserved host, or null if no host is reserved.

Release Host Reservation

Synopsis

public boolean releaseHostReservation()

Description

Call this function to release the current host reservation for the calling thread.

Upon successful completion, subsequent CCA calls made by the thread are included in the normal load-balancing performed by the CC ACSP Java Client.

Return Value

On successful completion, this function returns true. If the function fails, it returns false.

Host Reservation Examples

Reserving a Host

To direct a sequence of jCCA calls to a specific host (CC ACSP server), call the setHostReservation function as in the following example.

ClientController acspCtrl=ClientController.getInstance();
if (!acspCtrl.setHostReservation(host)){
    System.out.println("Reservation of host "+host+" failed");
    return;
}

Releasing a Host Reservation

To stop sending jCCA calls to a specific host (CC ACSP server), call the releaseHostReservation function as in the following example.

ClientController acspCtrl=ClientController.getInstance();
if (!acspCtrl.releaseHostReservation()){
    System.out.println("Release of host failed");
    return;
}

Retrieving the Current Host Reservation

The host reservation API provides the getHostReservation function for retrieving the current host-reservation as in the following example.

ClientController acspCtrl=ClientController.getInstance();
String reservation=acspCtrl.getHostReservation();
if (reservation==null){
    System.out.println("Reserved host: NONE");
} else {
    System.out.println("Reserved host: "+reservation);
}

Retrieving the Host List

The host reservation API provides the getHostList function for retrieving the current host list as in the following example.

ClientController acspCtrl=ClientController.getInstance();
String[] hostlist = acspCtrl.getHostList();

Iterating the Host List

Once the host list has been obtained, it can be used to iterate over the available hosts.

For each entry in the host list, the host name should be passed to the setHostReservation function before any jCCA verbs are called.

Before ending the iteration, the host reservation should be released as in the following example. If this is not done, the host reservation at the start of the next iteration will fail.

ClientController acspCtrl=ClientController.getInstance();
String[] hostlist = acspCtrl.getHostList();
for(int index=0;index<hostlist.length;index++){
    if (!acspCtrl.setHostReservation(host)){
        System.out.println("Reservation of host "+host+"failed");
        return;
    }
    try {
        //Call any sequence of jCCA verbs,
        //to have them executed on the reserved host.
    } finally {
        //Make sure that we release the host reservation,
        //even if an exception is thrown
        acspCtrl.releaseHostReservation();
    }
}

SetSslKeyStorePassword

Synopsis

public void setSslKeyStorePassword(String password)

Description

Call this function to set the JKS key-store password, of the key-store specified in the acsp.properties file by the “ssl.keyStore” property.

A call to this method will override the password specified by the “ssl.keyStorePassword” property in the acsp.properties file.

ClientController.getInstance().setSslKeyStorePassword("useThisPassword");

A call to this method will override the password specified by the “ssl.keyStorePassword” property in the acsp.properties file.

Note! The password must be set before a connection to the host is established, which is typically done when the first CCA verb is called.

GetSslKeyStorePassword

Synopsis

public String getSslKeyStorePassword()

Description

Call this function to get the JKS key-store password which has been set either by the getSslKeyStorePassword() method or the “ssl.keyStorePassword” property specified in the acsp.properties file.

String pw = ClientController.getInstance().getSslKeyStorePassword();

CloseConnectionPool

Synopsis

public boolean closeConnectionPool()

Description

Call this function to close all active connections to the specified server(s). This is normally not required, but can be used if application program does not call exit which automatically closes the connections.

If any CCA or ICSF verbs are called after this operation, then new connections to the CC ACSP server(s) will be established.

if (ClientController.getInstance().closeConnectionPool()) {
    System.out.println("done");
} else {
    System.out.println("did not do it");
}

GetConnectionList

Synopsis

public List<ServerData> getConnectionList()

Description

Call this function to get a List of connections to the specified CC ACSP server(s). ServerData is a communication object which holds diverse information about s connected server.

List<ServerData> connections =
    ClientController.getInstance().getConnectionList<>();
for (ServerData sd : connections) {
    System.out.println(sd);
}

Sample Program Listings

Here are some sample code listings. Some trivial code and comments have been removed. The full sample code is provided in an installation samples library.

Mac Sample Server Code

import com.ibm.crypto.hardware.cca.*;
import com.ibm.crypto.hardware.common.*;

public class Mac {
    public static byte[] mactest(byte[] macData) {
    
        byte[] ret = new byte[0];
        CCAKeyToken macKeyToken = null;
	
        CSNBKTB ktb = new CSNBKTB();
        try {
            System.out.println("Calling CSNBKTB for generating skeleton");
            ktb.addRule(CSNBKTB.INTERNAL);
            ktb.addRule(CSNBKTB.NO_CV);
            ktb.setKeyType(CSNBKGN.DATAM);
            ktb.execute();
        } catch (CCAException ce) {
            System.out.println(ktb.getVerbName()+" failed - \n"+ce);
            return ret;
        }
    
        CSNBKGN kgn = new CSNBKGN();
        try {
            System.out.println("Calling CSNBKGN for generating MAC key");
            kgn.setKeyForm(CSNBKGN.OP);
            kgn.setKeyLength(CSNBKGN.KEYLN16);
            kgn.setKeyType1(CSNBKGN.TOKEN);
            kgn.setGeneratedKey1(ktb.getKeyToken());
            kgn.execute();
            macKeyToken = kgn.getGeneratedKey1();
            System.out.println("MAC key generated");
        } catch (CCAException ce) {
            System.out.println(kgn.getVerbName()+" failed - \n"+ce);
            return ret;
        }

        // create the MAC
        CSNBMGN mgn = new CSNBMGN();
        try {
            System.out.println("Calling CSNBMGN for MAC method");
            mgn.addRule(CSNBMGN.ONLY);
            mgn.addRule(CSNBMGN.MACLEN8);
            mgn.addRule(CSNBMGN.X9_19OPT);
            mgn.setKey(macKeyToken);
            mgn.setText(macData);
            mgn.execute();
            ret = mgn.getMAC();
            System.out.println("Generated MAC = "+CCAUtil.B2X(ret));
        } catch (CCAException ce) {
            System.out.println(mgn.getVerbName()+" failed - "+ce);
            return ret;
        }
        
        CSNBMVR mvr = new CSNBMVR();
        try {
            System.out.println("Calling CSNBMVR to verify MAC method");
            mvr.addRule(CSNBMGN.ONLY);
            mvr.addRule(CSNBMGN.X9_19OPT);
            mvr.setKey(macKeyToken);
            mvr.setText(macData);
            mvr.setMAC(ret);
            mvr.execute();
            System.out.println("MAC verified OK");
        } catch (CCAException ce) {
            System.out.println(mvr.getVerbName()+" failed - "+ce);
        }
        return ret;
    }
    /**
     * Starts the application.
     * @param args an array of command line arguments
     */
    public static void main(java.lang.String[] args) {
        String macText = "I've got blisters on my fingers.";
        CCADebug.enable();
        System.out.println("Generated/verified MAC = " +
            CCAUtil.B2X(mactest(macText.getBytes())));
    }
}

CallUdfMac Sample Client Code

CallUdfMac demonstrates how to call a UDF from the client side by using a TLV structure and UDFCALL pseudo-verb provided by the jCCA framework. Compared to the Mac Sample Server Code, this client code together with its respective server code demonstrate how to convert the Mac program into a UDF function. This sample program calls the UDF named UdfMac described in the UdfMac Sample. Further, it demonstrates how to handle the local UDF execution as oppose to normal execution on the CC ACSP server.

For a general description of how to call the UDFCALL pseudo verb, please refer to “Error: Reference source not found” on page Error: Reference source not found.

The CallUdfMac class has the following method defined:

  • main:

The main method sets up local execution of the UDF, by instantiating an object of the UdfCallExecutor class and pass it to the setter method of the CCACommon class provided by jCCA. An object of the CallUdfMac class is instantiated and used for calling its MAC specific methods.

  • execute:

The execute function is a UDF utility function that encapsulates the UDFCALL pseudo-verb. It executes a UDF and demonstrates how data can be send using the TLVContainer class provided by the jCCA framework.

  • generateMacKey:

A method that given a key label, generates a key to be used for generating a Message Authentication Code (MAC). It places the generated key to key store unless the key label already exist (i.e. no replace).

  • generateMac:

A method that given a key label and data, generates a MAC associated to the data.

  • verifyMac:

A method that given a key label, data, and MAC value, verifies the data against the MAC value.

Sending data with TLVContainer is shown in all three methods that handles the MAC related operations. When data is passed with this container they are further send to the execute method also described above.

package acsp.samples;
import com.ibm.crypto.hardware.generic.UDFCALL;
import com.ibm.crypto.utility.TLVContainer;
import com.ibm.acsp.client.udf.UdfCallExecutor;
import com.ibm.crypto.hardware.common.CCACommon;
import com.ibm.crypto.hardware.common.CCAException;
import com.ibm.crypto.hardware.common.CCAKeyLabel;
import com.ibm.crypto.hardware.common.CCAUtil;

import com.ibm.crypto.utility.UtilMethods;

public class CallUdfMac extends UtilMethods {

	// Fully qualified class name of UDFControl
	static final String UDFCLASS = "acsp.samples.udf.UdfMac";
	
	/**
     * Utility function to execute an UDF.
     * @param data the argument data which should be sent to the UDF
     * @param operation Array of names of the operations to perform inside the called UDF
     * @return The resulting UDFCALL after execution
     * @throws CCAException in case of CCA error
     */
    private static TLVContainer.ElementList execute(byte[] data, String ... operations) throws CCAException {
        UDFCALL call = new UDFCALL();
        call.setFunction(UDFCLASS);
        call.addRules(operations);
        if (data!=null) { 
        	call.setDataIn(data);
        }
        call.setDataOutLength(4096);
        call.execute();
        return TLVContainer.decode(call.getDataOut());
    }

    // Generate key to be used for a MAC
    public void generateMacKey(CCAKeyLabel keylabel) {
    	TLVContainer con = new TLVContainer();
    	    	
    	try {
    		con.addElement(keylabel);
    		execute(con.encode(), "GENKEY");
    		say("%s: Generated MAC key OK%n",this.getClass().getName());
    	} catch(CCAException e) {
    		say("%s: %s",this.getClass().getName(),e);
    	}
    }
		
    // Generate MAC value
	public byte[] generateMAC(CCAKeyLabel keylabel,byte[] macData) {
		TLVContainer con = new TLVContainer();
		byte[] mac = new byte[0];
		
		try {
			con.addElement(keylabel);
			con.addElement(macData);
			TLVContainer.ElementList res = execute(con.encode(), "MACGEN");
			mac = res.getBytes(0);
			say("%s: Generated MAC value = %s%n",this.getClass().getName(),CCAUtil.B2X(mac));
			
		} catch(CCAException e) {
			say("%s: %s",this.getClass().getName(),e);
		}
		return mac;
	}
		
	// Verify a MAC value
	private void verifyMac(CCAKeyLabel keylabel,byte[] macData, byte[] mac) {
		TLVContainer con = new TLVContainer();
		
		try {
			con.addElement(keylabel);
			con.addElement(macData);
			con.addElement(mac);
			execute(con.encode(), "MACVER");
			say("%s: MAC verified OK%n",this.getClass().getName());
		} catch(CCAException e) {
			say("%s: %s",this.getClass().getName(),e);
		}
	}
			
	public static void main(java.lang.String[] args) {

		// Enable local execution
		CCACommon.setUdfExecutor(new UdfCallExecutor());
		
		String macText = "I've got blisters on my fingers.";
		CCAKeyLabel keyLabel = new CCAKeyLabel("ACSP.CLIENT.MACKEY08");
		CallUdfMac udfMacSample = new CallUdfMac();
		
		// Get key for MAC
		udfMacSample.generateMacKey(keyLabel);
		
		// Generate MAC from key label
		byte[] mac = udfMacSample.generateMAC(keyLabel,macText.getBytes());
		
		// Verify MAC 
		udfMacSample.verifyMac(keyLabel,macText.getBytes(), mac);
	}
}

UdfMac Sample UDF Server Code

The UdfMac class demonstrates how to create a CC ACSP UDF server function using the TLVContainer to send input parameters and receive return values.

For a general description of how to call the UDFCALL pseudo-verb, please refer to section 4.2 Calling a UDF from the CC ACSP Java Client.

The UdfMac sample program is the server code of the UDF. It implements the following functions, which can be called from the client side:

  • GENKEY: Given a key label, generate a key to use for MAC
  • MACGEN: Given a key label and MAC data, generate a MAC
  • MACVER: Given a key label, MAC data and MAC, verify the MAC on the MAC data.

The parameters key label, MAC data, and MAC are packed and sent via a TLVContainer provided by the jCCA framework. The UDF declares a private variable in its class definition from the nested class ElementList of the TLVConatiner class, and applies the respective getter methods to read the passed data.

The UdfMac has the following methods:

  • execute():

This is the driver method of the UDF, which executes the UDF server code when called from the client side by the UDFCALL pseudo-verb. This is equivalent to the main application method. In UdfMac the rules are analyzed, and calls to other methods are performed, otherwise a CCAException is thrown to indicate invalid rules.

  • generateMacKey():

This method is called when rule “GENKEY” is passed. It generates an internal DES key to be used for a MAC and saves it to key store, however, if the key label passed already exists a CCAException is thrown (i.e. no replace).

  • generateMac():

This method is called when rule “MACGEN” is passed. It generates a MAC from the given key label and data, and returns the MAC value. Otherwise it throws a CCAException.

  • VerifyMac():

This method is called when “MACVER” is passed. It verifies a MAC on the associated data and key label. Otherwise it throws a CCAException.

A general description of the logic of a UDF server function is:

  • Override the execute method of the AUdfFunction framework class to analyze input parameters to control the logic.
  • Utility methods from the framework can be called to provide session data to produce log data from the UDF function, for example:
    • logWarn(message,args)
    • logInfo(message,args)
    • logError(message,args)
    • logDebug(message,args)
    • logTrace(message,args)
    • getRules()
    • setRules(String[])
    • queryRule(String)
    • getDataIn()
    • setDataIn(byte[])
    • getDataOut()
    • setDataOut(byte[])
    • setUserID(String)
    • getUserID()
    • getKeyLabelAccess(label)
    • getProperty(key)
  • All jCCA verbs are available for call of CCA functions, and utility functions in the CCAUtil are also helpful.
  • Processing input and output data fields provides the flexibility needed for passing single or multiple input parameters or return parameters.
  • Exceptions are controlled through CCAExceptions, with a possibility for user defined return and reason codes.
package acsp.samples.udf;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.ibm.acsp.udf.AUdfFunction;
import com.ibm.crypto.hardware.common.CCAException;
import com.ibm.crypto.hardware.cca.CSNBKGN;
import com.ibm.crypto.hardware.cca.CSNBKTB;
import com.ibm.crypto.hardware.cca.CSNBMGN;
import com.ibm.crypto.hardware.cca.CSNBMVR;
import com.ibm.crypto.hardware.common.CCAKeyLabel;
import com.ibm.crypto.utility.KeyUtil;
import com.ibm.crypto.utility.TLVContainer;

public class UdfMac extends AUdfFunction {

	private static final Logger logger = LogManager.getLogger("UdfMac");
	private TLVContainer.ElementList inArgs;
	
	/**
	 * Execute the UDF function using TLV structure
	 * @throws CCAException
	 */
	@Override
	public void execute() throws CCAException {
	    setLogger(logger);
	    inArgs = TLVContainer.decode(getDataIn());
				
	    if(queryRule("GENKEY")) {
	        generateMacKey();
	    } else
	    if(queryRule("MACGEN")) {			
	        generateMac();
	    } else
	    if(queryRule("MACVER")) {
	        verifyMac();
	    }
	    else {
	        throw new CCAException(8,33);	// invalid rule
	    }
	}
	

	/**
	 * Generate MAC key and store in key store
	 * Input data: key-label
	 * @throws CCAException on CCA error
	 */
	private void generateMacKey() throws CCAException {
	    // get packed TLV input
	    CCAKeyLabel keylabel = inArgs.getCCAKeyLabel(0);

	    // check if key with label already exists - no replace
	    if(KeyUtil.keyExistDES(keylabel)) {
			throw new CCAException(8, 44);
	    }
		
	    // build MAC key skeleton
	    CSNBKTB ktb = new CSNBKTB();
	    ktb.addRules(CSNBKTB.INTERNAL,CSNBKTB.NO_CV);
	    ktb.setKeyType(CSNBKTB.DATAM);
	    ktb.execute();
		
	    // generate the MAC key using skeleton
	    CSNBKGN kgn = new CSNBKGN();
	    kgn.setKeyForm(CSNBKGN.OP);
	    kgn.setKeyLength(CSNBKGN.KEYLN16);
	    kgn.setKeyType1(CSNBKGN.TOKEN);
	    kgn.setGeneratedKey1(ktb.getKeyToken());
	    kgn.execute();
	    // save key as label into key store
	    if( !KeyUtil.putKeyDES(keylabel, kgn.getGeneratedKey1()) ) {
	        throw new CCAException(32,500,"Error storing generated key as 			"+keylabel);
	    }
		
	    // indicate successful termination by resetting DataOut and 
      // return/reason code
	    setDataOut(new byte[0]);
	    getInstance().setRR(0,0);
	}
	
	/**
	 * Generate MAC using key label and send back the MAC
	 * Input data: key-label, data
	 * output data: MAC
	 * @throws CCAException on CCA error
	 */
	private void generateMac() throws CCAException {
	    // get input data from TLV structure
	    CCAKeyLabel keylabel = inArgs.getCCAKeyLabel(0);
	    byte[] macData = inArgs.getBytes(1);
		
	    // generate MAC 				
	    CSNBMGN mgn = new CSNBMGN();
	    mgn.addRules(CSNBMGN.ONLY,CSNBMGN.MACLEN8,CSNBMGN.X9_19OPT);
	    mgn.setKey(keylabel);
	    mgn.setText(macData);
	    mgn.execute();
		
	    // return MAC in TLV structure
	    TLVContainer outArgs = new TLVContainer();
	    outArgs.addElement(mgn.getMAC());
	    setDataOut(outArgs.encode());
	    getInstance().setRR(0,0);
	}

	/**
	 * Verify MAC using key label
	 * Input data: key-label, data, MAC
	 * @throws CCAException on CCA error
	 */
	private void verifyMac() throws CCAException {
		
	    // get input data from TLV structure
	    CCAKeyLabel keylabel = inArgs.getCCAKeyLabel(0);
	    byte[] macData = inArgs.getBytes(1);
	    byte[] mac = inArgs.getBytes(2);
		
	    // verify the MAC
	    CSNBMVR mvr = new CSNBMVR();
	    mvr.addRules(CSNBMGN.ONLY,CSNBMGN.X9_19OPT);
	    mvr.setKey(keylabel);
	    mvr.setText(macData);
	    mvr.setMAC(mac);
	    mvr.execute();
	    // return 0 to indicate successful verification
	    setDataOut(new byte[0]);
	    getInstance().setRR(0,0);
	}
}

AgileClient Sample Client Code

AgileClient Sample Description

AgileClient demonstrates how to manage UDF properties on the server side and provides an example how it can be used to provide extra flexibility in configuring and controlling digital signature generation and verification. At the same time it also provides an example of handling the user defined reason codes on the client side.

The AgileClient class has the following methods defined:

  • main:

The main method sets up local execution of the UDF, by instantiating an object of the UdfCallExecutor class and pass it to the setter method of the CCACommon class provided by jCCA. An object of AgileClient class is instantiated for user interaction using the command line.

  • interact:

The interact method basically is a loop of reading the user commands and processing them. Two special commands are defined to quit the loop and shut down the program - 'x', 'q'.

  • readCommand:

A method where user input is awaited and user command string is returned after one has been entered.

  • processCommand:

This method handles the user commands that have been read from the command line. It treats the first token of the input as the command itself and the second as an option for that command. If it fails to understand the command, it will output the list of all commands available. PKA key generation and deletion, use value switching is also executed inside this method.

  • sayCCAException:

sayCCAException method is used to handle the reason codes returned within a CCAException that might be thrown while the server side code is called.

  • execute:

The execute function is a UDF utility function that encapsulates the UDFCALL pseudo-verb. It executes a UDF and demonstrates how data can be send using the TLVContainer class provided by the jCCA framework.

  • listProperties:

A method that will output all the UDF properties as key-value pairs.

  • getProp:

A method that will output a value of a property.

  • setProp:

A method that will change a value of a property. It can also be used to delete a property by providing an empty string as the new value.

  • copyProp:

copyProp method copies a complete property set.

  • getUseValues:

A method that will list all the encryption scheme options which are stored inside 'use.values' UDF property.

  • checkUseValue:

A method to check whether specific use value is present in 'use.values' UDF property.

  • sign:

A method that given data in a form of a byte array, generates a digital signature associated to the data along with the use value.

  • verify:

A method that given data, use value and signature, verifies the data against the signature.

  • checkKeys:

A method that lists PKA keys referenced by the use values.

AgileClient Sample Code

package acsp.samples;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.ibm.acsp.client.udf.UdfCallExecutor;
import com.ibm.crypto.hardware.api.TargetInfo;
import com.ibm.crypto.hardware.common.CCACommon;
import com.ibm.crypto.hardware.common.CCAException;
import com.ibm.crypto.hardware.common.CCAUtil;
import com.ibm.crypto.hardware.generic.UDFCALL;
import com.ibm.crypto.utility.TLVContainer;
import com.ibm.crypto.utility.UtilMethods;

/**
 * Communication client to send command input to a peer and display whatever replies returned.
 * The peer is expected to always return a response to each single line request.
 */
public class AgileClient extends UtilMethods
{

    // Fully qualified class name of UDFControl
    private static String ivUdfClass 	     = "";
	  private static final String UDFCLASS_LOCAL = "acsp.samples.udf.AgileUDF";
	  private static final String UDFCLASS_SERVER  = "com.ibm.acsp.server.udf.AgileUDF";

    private byte[] signature = null;
    private String with = null;

    private byte[] signtext = CCAUtil.S2B("There is plasters on my fingers");

   /**
     * Utility function to execute an UDF.
     * @param data the argument data which should be sent to the UDF
     * @param operations Array of names of the operations to perform inside the called UDF
     * @return The resulting UDFCALL after execution
     * @throws CCAException in case of CCA error
     */
    private static TLVContainer.ElementList execute(byte[] data, String ... operations) throws CCAException {
        UDFCALL call = new UDFCALL();
        call.setFunction(ivUdfClass);
        call.addRules(operations);
        if (data!=null) { 
            call.setDataIn(data);
        }
        call.setDataOutLength(4096);	// size enough
        call.execute();
        if(call.getReturnCode() > 0) {  // check for failures after executing via UDFCallExecutor
			throw new CCAException(call.getReturnCode(), call.getReasonCode());
		}
        return TLVContainer.decode(call.getDataOut());
    }


    private void checkKeys() {
        TLVContainer.ElementList res = null;
        try {
            res = execute(null,"CHECK");
            List<String> list = new ArrayList<>();
            for (int i=0;i<res.size();i++) {
                list.add(res.getString(i));
            }
            Collections.sort(list);
            say("List keys referenced by use values - count(%s):%n",list.size());
            for (String s : list) {
                say("  %s%n",s);
            }
        } catch (CCAException e) {
            say("CHECK function failed with: %s%n",sayCCAException(e));
        }
    }

    private void listProperties() {
        TLVContainer.ElementList res = null;
        try {
            res = execute(null,"LISTPROP");
            List<String> list = new ArrayList<>();
            for (int i=0;i<res.size();i++) {
                list.add(res.getString(i));
            }
            Collections.sort(list);
            say("List contents of properties:%n");
            for (String s : list) {
                say("  %s%n",s);
            }
        } catch (CCAException e) {
            say("LISTPROP function failed with: %s%n",sayCCAException(e));
        }
    }

    private String getProp(String key) {
        TLVContainer con = new TLVContainer();
        con.addElement(key);

        say("Getting property key: %s%n",key);
    	
        TLVContainer.ElementList res = null;
        try {
            res = execute(con.encode(),"GETPROP");
            String value = res.getString(0); 
            say("  value of property key %s is '%s' %n",key,value);
            return value;
        } catch (CCAException e) {
            say("GETPROP function failed with: %s%n",sayCCAException(e));
        }
         return "";
    }

    private void copyProp(String from,String to){
        TLVContainer con = new TLVContainer();
        con.addElement(from);
        con.addElement(to);
        say("Copying property set '%s.*' to property set '%s.*'%n",from,to);
    	
        try {
            execute(con.encode(),"COPYPROP");
        } catch (CCAException e) {
            String msg = e.toString();
            if (e.isRR(32, 1833)) {
                msg = String.format("from use set(%s) is not found",from);
            } else
            if (e.isRR(32, 1835)) {
                msg = String.format("to use set(%s) is not found",to);
            }
            say("COPYPROP function failed with: %s%n",msg);
        }
    }

    private void setProp(String key,String value){
        TLVContainer con = new TLVContainer();
        con.addElement(key);
        con.addElement(value);

        if (value.isEmpty()) {
            say("Deleting property key: %s%n",key);
        } else {
            say("Setting property key: %s to '%s'%n",key,value);
        }
    	
        try {
            execute(con.encode(),"SETPROP");
        } catch (CCAException e) {
            say("SETPROP function failed with: %s%n",sayCCAException(e));
        }
    }

    private TLVContainer.ElementList sign(byte[] data){
        TLVContainer con = new TLVContainer();
        con.addElement(data);
        TLVContainer.ElementList res = null;
        try {
            res = execute(con.encode(),"SIGN");
            signature = res.getBytes(0);
            with = res.getString(1);
            say("Signing data with key: %s%n",with);
            String signatureText = CCAUtil.B2X(signature);
            say("Signature is: %s%n", signatureText);
        } catch (CCAException e) {
            say("SIGN function failed with: %s%n",sayCCAException(e));
        }
        return res;
    }

    private TLVContainer.ElementList verify(String meta, byte[] data, byte[] signature) {
        TLVContainer con = new TLVContainer();
        TLVContainer.ElementList res = null;
        if(signature != null) {
            con.addElement(data);
            con.addElement(signature);
            con.addElement(meta);
            say("Verifying signature with key: %s%n",meta);
            try {
                res = execute(con.encode(),"VERIFY");
            } catch (CCAException e) {
                say("VERIFY function failed with: %s%n",sayCCAException(e));
            }
        }
        else {
                  say("Please sign text first!%n");
        }
        return res;
    }

    private String[] getUseValues() {
        String valid = getProp("use.values");
        if (!valid.isEmpty()) {
            return valid.split(",");
        }
        return new String[]{};
    }

    private boolean checkUseValue(String use) {
        String[] valid = getUseValues();
        if (valid.length>0) {
            return CCAUtil.inList(use, false, valid);
        }
        return false;
    }
	
    private void processCommand(String[] args) /*throws CCAException*/ {
    	
        String cmd = args.length>0 ? args[0].toUpperCase() : "";
        String opt = args.length>1 ? args[1].toUpperCase() : "";
    	
        if (cmd.startsWith("C")) {
    	    checkKeys();        		
        } else

        if (cmd.startsWith("G") && args.length>1) {
            String useVal = args[1];
            if (checkUseValue(useVal)) {
                TLVContainer con = new TLVContainer();
                con.addElement( useVal);
                try {
                    execute(con.encode(),"GENKEY");
                    say("%s key has been generated%n", useVal);
                } catch (CCAException e) {
                    say("GENKEY function failed with: %s%n",sayCCAException(e));
                }
            }
            else {
                say("Invalid use value(%s) %n",useVal);
            }
        } else
    		
        if (cmd.startsWith("D") &&  args.length>1) {
            String useVal = args[1];
            if (checkUseValue(useVal)) {
                TLVContainer con = new TLVContainer();
                con.addElement( useVal);
                try {
                    execute(con.encode(),"DELKEY");
                } catch (CCAException e) {
                    say("DELKEY function failed with: %s%n",sayCCAException(e));
                }
            }
            else {
                say("Invalid use value(%s) %n",useVal);
            }
        } else

        if (cmd.startsWith("U") &&  args.length>1) {
            String useVal = args[1];
            if (checkUseValue(useVal)) {
                TLVContainer con = new TLVContainer();
                con.addElement(useVal);
                try {
                    execute(con.encode(),"USEKEY");
                    say("Using %s encryption%n", useVal);
                } catch (CCAException e) {
                    say("USEKEY function failed with: %s%n",sayCCAException(e));
                }
            }
            else {
                say("Invalid use value(%s) %n",useVal);
            }
        } else

        if (cmd.startsWith("P")) {
            if (opt.startsWith("C") && args.length>3) {
                copyProp(args[2],args[3]);
            } else
            if (opt.startsWith("L")) {
                listProperties();        		
            } else
            if (opt.startsWith("G") && args.length>2) {
                getProp(args[2]);
            } else
            if (opt.startsWith("S") && args.length>3) {
                setProp(args[2], args[3]);
            } else
            if (opt.startsWith("D") && args.length>2) {
                setProp(args[2], "");
            } else
            if (opt.startsWith("I")) {
                try {
                    execute(null,"SETUP");
                } catch (CCAException e) {
                    say("SETUP function failed with: %s%n",sayCCAException(e));
                }
            }
            else {
                say("property option %s is invalid - use copy init, list, get, set or del%n",opt);
            }
        } else
        if (cmd.startsWith("S")) {
            signtext = CCAUtil.S2B(readData());
            sign(signtext);
        } else
        if (cmd.startsWith("V")) {
            verify(with, signtext, signature);
        }
        else {
            say("Commands are:%n"
                +"  del <use ref>         - delete generated key%n"
                +"  use <use ref>         - set use key for sign/verify%n"
                +"  gen <use ref>         - generate/store key for use value%n"
                +"  check                 - check and list keys for use values%n"
                +"  prop copy <from> <to> - copy use set <from> <to>%n"
                +"  prop init             - initiate properties with defaults%n"
                +"  prop list             - list all properties%n"
                +"  prop del <name>       - delete single property%n"
                +"  prop get <name>       - get single property%n"
                +"  prop set <name> <val> - set property value%n"
                +"  sign                  - sign with current key (call before verify)%n"
                +"  ver                   - verify signature with current key%n"
                +"  x,q                   - exit%n"
            );
        }
    }

    public void interact() {
        say("Starting Agile Client -%n%n" + TargetInfo.info() + "%n%n");
        String[] args = readCommand();
        do {
            processCommand(args);
            args = readCommand();
       	
        } while (args.length>0 && !CCAUtil.inList(args[0],false,"x","q"));
            say("Terminating Agile client.%n");
    }
    
    private String[] readCommand() {
        String[] cmds;
        int lslen = System.getProperty("line.separator").length();
        say("Enter Agile command or type 'help' for command list: %n");
        try {
            byte[] ab = new byte[132];
            int br = System.in.read(ab);

            if (br<0 || ab.length==0) {
                cmds = new String[0];
            } 
            else {
                ab = CCAUtil.copy(ab, 0, br-lslen);
                cmds = CCAUtil.B2S(ab).split(" ");
            }
        }
        catch (IOException ioe) {
            cmds = new String[0];
        }
        return cmds;
    }
    
    private String readData() {
        say("Enter text to sign: ");

        String text = "";

        int lslen = System.getProperty("line.separator").length();

        try {
            byte[] ab = new byte[132];
            int br = System.in.read(ab);

            ab = CCAUtil.copy(ab, 0, br - lslen);
            text = CCAUtil.B2S(ab);

        } catch(IOException ioe) {
            say("Could not read text, will use '%s'", CCAUtil.B2S(signtext));
        }

        return text;
    }
    
    private String sayCCAException(CCAException e) {
        if (e.isRR(32, 1831)) {
            return "The key already exist";
        } else
        if (e.isRR(32, 1833)) {
            return "Invalid use value";
        } else
        if (e.isRR(32, 1834)) {
            return "Invalid gen parms";
        } else
        if (e.isRR(32, 1835)) {
            return "use value already exist";
        }
        return e.toString();	
    }
    
	/**
	 * Connects to and communicates with any peer using any protocol. Accepts input from stream, sends it to peer and
	 * displays all received responses on console.
	 * @param args command args
	 */
    public static void main(String[] args) {
    if (args.length>0 && args[0].equalsIgnoreCase("server")) {
			ivUdfClass = UDFCLASS_SERVER;
			say("UDF will be executed remotely%n");
	  }
    else {
        // Enable local execution
        CCACommon.setUdfExecutor(new UdfCallExecutor());
        ivUdfClass = UDFCLASS_LOCAL;
	      say("UDF will be executed locally%n");
    }
    new AgileClient().interact();
    }
}

AgileUDF Sample UDF Server Code

AgileUDF Sample Description

The AgileUDF class demonstrates the use of the UDF properties. The AgileUDF sample program is the server code of the UDF. It implements the following rules, which can be called from the client side:

  • CHECK: return PKA keys referenced by the use values
  • SETUP: initialize sample properties
  • GENKEY: given a use value generate a PKA key to sign and verify data
  • USEKEY: switch use value to the one provided
  • DELKEY: given use value delete the associated PKA key
  • SIGN: given the use value and data, generate a digital signature
  • VERIFY: given the use value, data and the signature, verify the signature on the data
  • COPYPROP: copy property set of a use value to a new one
  • GETPROP: return value of a specific property
  • SETPROP: set a new value for specific property
  • LISTPROP: return all the properties

The UDF defines a set of private constants for user-defined reason codes to be returned within CCAExceptions for handling unwanted cases related to the UDF properties.

The execute method decodes the TLVContainer with incoming parameters from the client side into an ElementList and passes it to some of the UDF rule handling functions where these parameters are necessary.

The AgileUDF has the following methods:

  • execute():

This is the driver method of the UDF, which executes the UDF server code when called from the client side by the UDFCALL pseudo-verb. In AgileUDF the rules are analyzed and calls to other methods are performed, otherwise a CCAException is thrown to indicate invalid rules.

  • setupProperties():

This method is called when rule "SETUP" is passed. It initializes the sample UDF properties including the use value and the use values options available with rules and parameters for PKA key generation, hashing, signing and verification.

  • useKey():

This method is called when rule "USEKEY" is passed. It checks if the use value passed is in the list of available options and updates the relevant property.

  • checkKeys():

This method is called when rule "CHECK" is passed. For each use value option it retrieves a PKA key label stored in the associated property and checks if the PKA key itself is present using the keyExistPKA method from the jCCA KeyUtil class.

  • genKEY():

This method is called when rule "GENKEY" is passed. For a given use value it generates a PKA key based on the relevant rules and parameters and store it in the PKA storage.

  • deleteKey():

This method is called when rule "DELKEY" is passed. For a given use value it retrieves the PKA key label and calls the deletePKA method from the jCCA KeyUtil class to delete the associated PKA key inside the PKA storage.

  • sign():

This method is called when rule "SIGN" is passed. It generates a signature from data and a key label referenced by the given use value. If use value is not provided, it retrieves one from the related UDF property.

  • verify():

This method is called when rule "VERIFY" is passed. It verifies a signature on the associated data and key label referenced by the given use value. If the use value is not provided, it retrieves one from the related UDF property.

  • listProperties():

This method is called when rule "LISTPROP" is passed. It retrieves all the UDF properties and encodes them in the output TLVContainer.

  • getProperty():

This method is called when rule "GETPROP" is passed. For a given UDF property key it returns its value.

  • setProperty():

This method is called when rule "SETPROP" is passed. For a given UDF property key it updates its value the one provided. Upon completion it saves the UDF property file to persist the changes.

  • copyProperty():

This method is called when rule "COPYPROP" is passed. It copies the whole UDF property set associated with one use value to a new one. It throws CCAException if the source use value does not exist or target use value exists.

A general description of the logic of a UDF server function is:

  • Override the execute method of the AUdfFunction framework class to analyze input parameters to control the logic.
  • Utility methods from the framework can be called for property management, for example:
  • saveProperties();

  • getProperties();

  • getProperty(String);

  • setProperty(String, String);

  • Utility functions in the KeyUtil are helpful when executing key handling tasks.
  • UDF properties provides additional flexibility in configuring the UDF function. As this configuration is persisted locally, there is no need to send it from the client each time.

AgileUDF Sample Code

package acsp.samples.udf;

import static com.ibm.acsp.client.ConstClient.*;

import com.ibm.acsp.udf.AUdfFunction;
import com.ibm.crypto.hardware.cca.*;
import com.ibm.crypto.hardware.common.CCAException;
import com.ibm.crypto.hardware.common.CCAKeyLabel;
import com.ibm.crypto.hardware.common.CCAUtil;
import com.ibm.crypto.utility.KeyUtil;
import com.ibm.crypto.utility.TLVContainer;

import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;


public class AgileUDF extends AUdfFunction {

    
    private static final String DELIM = ",";

    // Agile UDF
    private static final int ERROR_UDF_AGILE_KEY_EXISTS         = 1831;
    private static final int ERROR_UDF_AGILE_NOT_SAVED            = 1832;
    private static final int ERROR_UDF_AGILE_INVALID_USE_VALUE    = 1833;
    private static final int ERROR_UDF_AGILE_INVALID_GEN_PARMS    = 1834;
    private static final int ERROR_UDF_AGILE_USE_VALUE_EXISTS    = 1835;

    public AgileUDF() {
        // nothing
    }

    @Override
    public void execute() throws CCAException {
        TLVContainer.ElementList args = TLVContainer.decode(getDataIn());

        if (queryRule("CHECK")) {
            checkKeys();
        } else

        if (queryRule("SETUP")) {
            setupProperties();
        } else

        if (queryRule("GENKEY")) {
            genKEY(args);
        } else

        if (queryRule("USEKEY")) {
            useKey(args);
        } else
                
        if (queryRule("DELKEY")) {
            deleteKey(args);
        } else
                
        if (queryRule("SIGN")) {
            sign(args);
        } else 

        if (queryRule("VERIFY")) {
            verify(args);
        } else 
        
        if (queryRule("COPYPROP")) {
            copyProperty(args);
        } else 
            
        if (queryRule("GETPROP")) {
            getProperty(args);
        } else 
            
        if (queryRule("SETPROP")) {
            setProperty(args);
        } else 
            
        if (queryRule("LISTPROP")) {
            listProperties();
        } 
        
        else {
            throw new CCAException(8, 33);    // CCA invalid rule
        }
    }
    private void useKey(TLVContainer.ElementList args) {
        String use = args.getString(0);
        if (checkUseValue(use)) {
            try {
                setProperty("use", use);
                saveProperties();
            } catch (IOException e) {
                logError("Properties not saved - reason: %s", e);
            }
        }                
    }
    
    private void deleteKey(TLVContainer.ElementList args) {
        String use = args.getString(0);
        if (checkUseValue(use)) {
            String keylabel = getProperty(use + ".key","");
            if (KeyUtil.keyExistPKA(keylabel)) {
                logInfo("DELKEY: Key label %s %s", keylabel,KeyUtil.deletePKA(keylabel) ? "deleted":"NOT deleted");
            }
            else {
                logError("DELKEY: Key label %s not found", keylabel);
            }
        }                
    }
    
    private void genKEY(TLVContainer.ElementList args) throws CCAException {
        String use = args.getString(0);

        if (!checkUseValue(use)) {
            logError("GENKEY: Use value(%s) not defined in property use.values'", use);
            throw new CCAException(ACSP_RETURNCODE, ERROR_UDF_AGILE_INVALID_USE_VALUE);
        }
 
        String[] parms = getKeyParms(use);
        if (parms.length<3) {
            logError("GENKEY: Property value '%s.gen.parms' not defined or incomplete", use);
            throw new CCAException(ACSP_RETURNCODE, ERROR_UDF_AGILE_INVALID_GEN_PARMS);
        }
        String gentype = parms[0].toUpperCase();
        int genval1 = Integer.parseInt(parms[1]);
        int genval2 = Integer.parseInt(parms[2]);
         
        String keylabel = getProperty(use +".key","");
        String[] rules = getPropertyRules(use + ".gen.rules");

        if (KeyUtil.keyExistPKA(keylabel)) {
            logError("GENKEY: %s key already exist - Use function DEL %s to delete",keylabel,use);
            throw new CCAException(ACSP_RETURNCODE, ERROR_UDF_AGILE_KEY_EXISTS);
        }

        logInfo("GENKEY: Generating '%s' key",use);

        // build skeleton token
        CSNDPKB pkb = new CSNDPKB();
        pkb.addRules(rules);
        if (gentype.startsWith("E") ) {
            pkb.createECCKeyValueStructure(genval1, genval2);
            String name = genval1==0 ? "Prime" : 
                          genval1==1 ? "BrainPool" : 
                                       "Edwards"; 
            logDebug("GENKEY: ECC key structure skeleton used, curve=%s(%s), bits=%s",genval1, name, genval2);
        } else
        if (gentype.startsWith("R") ) {
            pkb.createRSAKeyValueStructure(genval1, genval2);
            logDebug("GENKEY: RSA key structure skeleton used, modulus=%s, exponent=%s",genval1, genval2);
        } else
        if (gentype.startsWith("Q") ) {
            pkb.createQSAKeyValueStructure(genval1, genval2);
            logDebug("GENKEY: QSA key structure skeleton used, algorithm=%s, format=%s",genval1, genval2);
        }
        pkb.execute();
        // generate key pair
        CSNDPKG pkg = new CSNDPKG();
        pkg.addRule(CSNDPKG.MASTER);
        pkg.setSkeletonKey(pkb.getKeyToken());
        pkg.execute();
        logDebug("GENKEY: %s key generated",gentype);
        // store key pair in pka storage
        CSNDKRC krc = new CSNDKRC();
        krc.setKeyToken(pkg.getGeneratedKeyToken());
        krc.setKeyLabel(CCAKeyLabel.valueOf(keylabel));
        krc.execute();
        logDebug("GENKEY: %s key stored with name: %s",gentype, keylabel);

        logInfo("%s key generated",gentype);
    }


    private void sign(TLVContainer.ElementList args) throws CCAException { 
        byte[] data = args.getBytes(0);
        String use = args.getString(1);

        if (use==null) {
            use = getProperty("use");
        }
        if (!checkUseValue(use)) {
            logError("Use value(%s) not defined in property use.values'", use);
            throw new CCAException(ACSP_RETURNCODE, ERROR_UDF_AGILE_INVALID_USE_VALUE);
        }
        
        String keylabel = getProperty(use + ".key","");
        String[] rules = getPropertyRules(use + ".sign.rules");

        logDebug("Create signature using %s with key label %s",use.toUpperCase(), keylabel);

        CSNDDSG dsg = new CSNDDSG();
        dsg.addRules(rules);
        dsg.setPrivateKey(CCAKeyLabel.valueOf(keylabel));
        
        if (queryRule("MESSAGE")) {
            dsg.setData(data);
        } else {
            byte[] hash = hash(data, getPropertyRules(use + ".hash.rules"));
            dsg.setData(hash);
        }
        dsg.execute();
        logInfo("Created signature using %s with key label %s",use.toUpperCase(), keylabel);
            
        // pack signature with meta data
        TLVContainer outArgs = new TLVContainer();
        outArgs.addElement(dsg.getSignature());
        outArgs.addElement(use);
        // set output
        setDataOut(outArgs.encode());
    }

    private void verify(TLVContainer.ElementList args) throws CCAException { 
        byte[] data = args.getBytes(0);
        byte[] signature = args.getBytes(1);
        String use = args.getString(2);

        if (use==null) {
            use = getProperty("use");
        }

        if (!checkUseValue(use)) {
            logError("Verify: Use value(%s) not defined in property use.values'", use);
            throw new CCAException(ACSP_RETURNCODE, ERROR_UDF_AGILE_INVALID_USE_VALUE);
        }

        
        String keylabel = getProperty(use + ".key","");
        String[] rules = getPropertyRules(use + ".verify.rules");

        logInfo("Verifying signature using %s with key label %s",use.toUpperCase(), keylabel);

        CSNDDSV dsv = new CSNDDSV();
        dsv.addRules(rules);
        dsv.setPublicKey(CCAKeyLabel.valueOf(keylabel));
        dsv.setSignature(signature);

        if (queryRule("MESSAGE")) {
            dsv.setData(data);
        } else {
            byte[] hash = hash(data, getPropertyRules(use + ".hash.rules"));
            dsv.setData(hash);
        }
        dsv.execute();
        logInfo("Verifying signature using %s with key label %s",use.toUpperCase(), keylabel);
    }
    
    private byte[] hash(byte[] text, String... rules) throws CCAException {
        CSNBOWH bowh = new CSNBOWH();

        bowh.addRules(rules);
        bowh.setText(text);
        bowh.execute();
        return bowh.getHash();
    }
    
    private String[] getPropertyRules(String key) {
        String rules = getProperty(key);
        rules = rules == null ? "" : rules;
        String[] res = rules.toUpperCase().split(DELIM);
        logInfo("rules = %s", Arrays.asList(res));
        return res;
    }

    private String[] getUseValues() {
        String valid = getProperty("use.values");
        if (valid!=null) {
            String[] args = valid.split(",");
            for (int i=0;i<args.length;i++) {
                args[i] = args[i].trim(); 
            }
            logDebug("use values = %s", Arrays.asList(args));
            return args;
        }
        logError("use values = NONE!!!");
        return new String[]{};
    }

    private String[] getKeyParms(String use) {
        String valid = getProperty(use + ".gen.parms");
        if (valid!=null) {
            String[] args = valid.split(",");
            if (args.length>2) {
                for (int i=0;i<args.length;i++) {
                    args[i] = args[i].trim(); 
                }
                return args;
            }
            logError("property '%s.gen.parms' too short",use);

        } else {
            logError("property '%s.gen.parms' not found",use);
        }
        return new String[]{};
    }

    private boolean checkUseValue(String use) {
        String[] valid = getUseValues();
        if (valid.length>0) {
            return CCAUtil.inList(use, false, valid);
        }
        return false;
    }

    private void setupProperties() throws CCAException {
        
        logInfo("Initializing sample properties");
        
        setProperty("use","rsa");
        setProperty("use.values","dsa,rsa,qsa");

        setProperty("dsa.key","ACSP.AGILEUDF.DSA");
        setProperty("dsa.gen.rules","ECC-PAIR,SIG-ONLY");
        setProperty("dsa.gen.parms","ecc,1,256");
        setProperty("dsa.sign.rules","ECDSA,MESSAGE,SHA-256");
        setProperty("dsa.verify.rules","ECDSA,MESSAGE,SHA-256");
        setProperty("dsa.hash.rules","SHA-256");

        setProperty("rsa.key","ACSP.AGILEUDF.RSA");
        setProperty("rsa.gen.rules","RSA-AESM,SIG-ONLY");
        setProperty("rsa.gen.parms","rsa,2048,65537");
        setProperty("rsa.sign.rules","PKCS-1.1");
        setProperty("rsa.verify.rules","PKCS-1.1");
        setProperty("rsa.hash.rules","SHA-256");

        setProperty("qsa.key","ACSP.AGILEUDF.QSA");
        setProperty("qsa.gen.rules","QSA-PAIR,U-DIGSIG");
        setProperty("qsa.gen.parms","qsa,1,2");
        setProperty("qsa.sign.rules","CRDL-DSA,MESSAGE,CRDLHASH");
        setProperty("qsa.verify.rules","CRDL-DSA,MESSAGE,CRDLHASH");
        setProperty("qsa.hash.rules","SHA-256");

        saveUDFProperties();
    }

    private void copyProperty(TLVContainer.ElementList args) throws CCAException {
        String from = args.getString(0);
        if (!checkUseValue(from)) {
            logError("from use value '%s' is invalid",from);
            throw new CCAException(32, ERROR_UDF_AGILE_INVALID_USE_VALUE);
        }
        String to = args.getString(1);
        if (checkUseValue(to)) {
            logError("to use value '%s' is already defined",to);
            throw new CCAException(32, ERROR_UDF_AGILE_USE_VALUE_EXISTS);
        }
        logInfo("copying all properties(6) from stanca=%s.* to new stanca=%s.*",from,to);

        setProperty(to + ".key"          ,getProperty(from + ".key",""));
        setProperty(to + ".gen.rules"    ,getProperty(from + ".gen.rules",""));
        setProperty(to + ".gen.parms"    ,getProperty(from + ".gen.parms",""));
        setProperty(to + ".sign.rules"   ,getProperty(from + ".sign.rules",""));
        setProperty(to + ".verify.rules" ,getProperty(from + ".verify.rules",""));
        setProperty(to + ".hash.rules"   ,getProperty(from + ".hash.rules",""));
        
        saveUDFProperties();
    }

    private void getProperty(TLVContainer.ElementList args) {
        String key = args.getString(0);
        String value = getProperty(key);
        logInfo("geting property key(%s) - value(%s)",key,value);
        
        TLVContainer outArgs = new TLVContainer();
        outArgs.addElement(value);
        setDataOut(outArgs.encode());
    }
    
    private void setProperty(TLVContainer.ElementList args) throws CCAException {
        String key = args.getString(0);
        String value = args.getString(1);
        String ovalue = getProperty(key);
        
        if (value.isEmpty()) {
            getProperties().remove(key);
            logInfo("remove property key(%s)",key);
        } else {
            logInfo("Setting property key(%s) to value(%s) - old value(%s)",key,value,ovalue);
            setProperty(key, value);
        }
        
        saveUDFProperties();
    }

    private void listProperties() {
        
        Properties prop = getProperties();
        // pack signature with meta data
        TLVContainer outArgs = new TLVContainer();
        for (String key :prop.stringPropertyNames()) {
            outArgs.addElement(key + " = " + getProperty(key));
        }
        // set output
        setDataOut(outArgs.encode());
        logInfo("List of property values returned - count(%s)",prop.size());
    }

    private void checkKeys() {
        int count = 0;
        TLVContainer out = new TLVContainer();
        for (String use : getUseValues()) {
            count++;
            String label = getProperty(use + ".key");
            if (label==null || label.isEmpty()) {
                out.addElement("%s.key : property not found.",use);
                continue;

            }
            out.addElement(String.format("%s.key = %s : key %s.",
                    use,label,KeyUtil.keyExistPKA(label) ? "present" : "not found"));
        }
        // set output
        setDataOut(out.encode());
        logInfo("List key referenced by use values - count(%s)",count);
        
    }

    
    private void saveUDFProperties() throws CCAException {
        try {
            saveProperties();
        } catch (IOException e) {
            logError("UDF properties not saved - reason: %s", e);
            throw new CCAException(ACSP_RETURNCODE, ERROR_UDF_AGILE_NOT_SAVED);
        }
    }
    
    /** Copyright IBM Corp. */
    public static String copyright() {return Copyright.IBM_COPYRIGHT;}
}

jCCA Generic UDX Verb

To accomplish easier calls to UDX functions via jCCA and remotely via CC ACSP Java and C client, the following generic verb was introduced in jCCA 1.6.4.

Description

The jCCA generic UDX verb, (UDXCALL) is used to call a UDX function through the jCCA API. The call to the function is done similarly to other CCA verb calls. All the CCA verbs in the jCCA API call the CCA API through a Java Native Interface, JNI, which is a DLL/so module loaded by the Java Runtime Environment (JRE).

When the UDXCALL verb is called, the jCCA JNI will look for and load a shared object (DLL) module called jccauxcl. The JRE will prefix the library name with lib, and use the suffix .so in the z/OS and Linux environments. A library name called:

libjccauxcl.so

will be loaded.

If the shared object is not found or is not loadable, the UDXCALL verb will return an error:

return_code = 20 reason_code = 20,

This is the jCCA way of signaling that the verb is not available on the current platform.

If the jccauxcl JNI module is found, it is loaded and the function name is checked for validity. The jccauxcl module returns an error using the return_code and reason_code fields if the function name is not found.

If the function name is valid, the data_input_length and data_input fields should be passed to the UDX function called for validation of the input data. Other return/reason codes indicating errors may result from the validation.

The data_output_length and data_output parameters are output fields for the UDX function which are returned as results to the caller.

Call Format

Call format in Java

See “Java jCCA API call and CC ACSP Java Client“ below.

Call format in C

void UDXCALL(
    long          * return_code, 
    long          * reason_code, 
    long          * exit_data_length, 
    unsigned char * exit_data, 
    long          * rule_array_count, 
    unsigned char * rule_array, 
    unsigned char * function, 
    long          * input_data_length, 
    unsigned char * input_data, 
    long          * output_data_length, 
    unsigned char * output_data);

Format

In the table below the parameters are listed with their name, whether input or output, data type and data length or value.

Parameter Name Direction Data Type Data Length or value
return_code output Integer
reason_code output Integer
exit_data_length in/output Integer
exit_data in/output String
rule_array_count input Integer

=0

rule_array input String rule_array_count * 8 bytes
function input String 8 bytes, blank padded
data_input_length input Integer
data_input input String
data_output_length in/output Integer
data_output in/output String

Parameters

In this section the parameters are explained.

return_code reason_code exit_data_length exit_data parameters See “Parameters common to all verbs” in “SC14-7508 z/OS ICSF Application Programmers Guide” or “CCA Basic Services Reference and Guide for the IBM 4770, IBM 4769, IBM 4767, and IBM 4765 PCIe Cryptographic Coprocessors Releases 8.4, 7.5, 7.4, 7.3, 7.2, 5.7, 5.6, 5.5, 5.4, 5.3, 4.4, and 4.2”.

rule_array_count The rule_array_count parameter is a pointer to an integer variable which contains the number of elements in the rule_array variable.

rule_array The rule_array parameter is a pointer to a string variable which contains an array of keywords. The keywords are 8 bytes in length, and must be left-aligned and padded on the right with space characters.

function The function parameter is a pointer to a string variable which contains a function name for the UDX function to be called. The function parameter is 8 bytes in length, and must be left-aligned and padded on the right with space characters.

data_input_lengthThe data_input_length parameter is a pointer to an integer variable which contains the length of the data_input variable.

data_input The data_input parameter is a pointer to a string variable which contains input parameters for the UDX function. The content of the data_input variable is defined by the UDX function.

data_output_length The data_output_length parameter is a pointer to an integer variable which contains the length of the data_output variable. At input this parameter should contain the maximum length of the data output which can be used for the output_data variable. At output this variable contains the length of the actually returned output length in the data_output variable.

data_output The data_output parameter is a pointer to a string variable containing output parameters for the UDX function. The content of the data_output variable is defined by the UDX function.

Java jCCA API call and CC ACSP Java Client

The UDXCALL verb is implemented in the CC ACSP Java Client through jCCA, which creates similar interfaces in the jCCA API and the CC ACSP Java Client.

The following is a sample skeleton:

UDXCALL udx = new UDXCALL();
try {
    udx.addRule("RULE1");
    udx.addRule("RULE2");
    udx.setFunction("SAMPLE");
    udx.setInputData(<byte array>);
    udx.execute();
    byte[] result = udx.gettOutputData();
    }
catch (CCAException ce) {
System.out.println("UDXCALL failed with exception: " + ce);
}

C API Call through CC ACSP client

The UDXCALL verb is implemented in the CC ACSP Java Client through jCCA, which creates similar interfaces in the jCCA API and the CC ACSP Java Client.

The UDXCALL verb is also implemented in the CC ACSP CCA C Client, for generic UDX calls in the CC ACSP C interface.

jccauxcl Sample code

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#if defined( _WIN32 )
    #include <io.h>
    #define DLLEXPORT __declspec(dllexport)
    #define DLLCALL __stdcall
#else /* UNIX... (aix, linux, zlinux, zos, os400) */
    #include <stdio.h>
    #define DLLEXPORT
    #define DLLCALL
#endif

#if defined(__MVS__)
    #define CCAI int
#else
    #define CCAI long
#endif

// prototypes
static void FUNC1 (
    CCAI          * return_code,
    CCAI          * reason_code,
    CCAI          * rule_array_count,
    char          * rule_array,
    char          * function,
    CCAI          * data_in_length,
    unsigned char * data_in,
    CCAI          * data_out_length,
    unsigned char * data_out
);
static void FUNC2 (
    CCAI          * return_code,
    CCAI          * reason_code,
    CCAI          * rule_array_count,
    char          * rule_array,
    char          * function,
    CCAI          * data_in_length,
    unsigned char * data_in,
    CCAI          * data_out_length,
    unsigned char * data_out
);

// helper functions
void print8(char* name) {
    int n = 0;
    for(;n<8;n++) printf("%c",name[n]);
}

void printRules(char* name,int cnt) {
    int j = 0;
    printf("**debug** Number of rules: %d\n",cnt);
    printf("**debug** Rules: ");
    if (cnt>0) {
        for(j=0;j<cnt;j++) {
            printf("'");print8(name+(j*8));printf("'");
            if (j<cnt-1) printf(",");
        }
    } else {
        printf("None");
    }
    printf("\n");
}

/* -------------------------------------------------------------------------- *
 * Generic UDX Call Function (UDXCALL)                                        *
 * -------------------------------------------------------------------------- */
DLLEXPORT void DLLCALL UDXCALL (
    CCAI          * return_code,
    CCAI          * reason_code,
    CCAI          * exit_data_length,
    unsigned char * exit_data,
    CCAI          * rule_array_count,
    char          * rule_array,
    char          * function,
    CCAI          * data_in_length,
    unsigned char * data_in,
    CCAI          * data_out_length,
    unsigned char * data_out
)
{
    int rcnt = *rule_array_count;

    printRules(rule_array,rcnt);
    printf("**debug** Function: '"); print8(function); printf("'\n");
    printf("**debug** Data input length: %ld\n",*data_in_length);
    printf("**debug** Data output length: %ld\n",*data_out_length);

    if (memcmp(function,"FUNC1",5)==0) {
        FUNC1(return_code,reason_code,
              rule_array_count,rule_array,function,
              data_in_length,data_in,
              data_out_length,data_out);
    } else

    if (memcmp(function,"FUNC2",5)==0) {
        FUNC2(return_code,reason_code,
              rule_array_count,rule_array,function,
              data_in_length,data_in,
              data_out_length,data_out);
    }
    else {
        printf("**debug** Undefined function name: '");
        print8(function);
        printf("'\n");
        *return_code = 20;
        *reason_code = 104;  // function not found
    }
}

/* -------------------------------------------------------------------------- *
 * UDXCALL - Sample function: FUNC1                                           *
 * -------------------------------------------------------------------------- */
static void FUNC1 (
    CCAI          * return_code,
    CCAI          * reason_code,
    CCAI          * rule_array_count,
    char          * rule_array,
    char          * function,
    CCAI          * data_in_length,
    unsigned char * data_in,
    CCAI          * data_out_length,
    unsigned char * data_out
)
{
    printf("**debug** Running sample generic UDX call function: FUNC1\n");
    if (*data_out_length >= 12) {
        memcpy(data_out,"FUNC 1 IS OK",12);
        *data_out_length = 12;
        *return_code = 0;
        *reason_code = 0;
    }
    else {
        *return_code = 8;
        *reason_code = 72;  // output size to small
    }
}

/* -------------------------------------------------------------------------- *
 * UDXCALL - Sample function: FUNC2                                           *
 * -------------------------------------------------------------------------- */
static void FUNC2 (
    CCAI         * return_code,
    CCAI          * reason_code,
    CCAI          * rule_array_count,
    char          * rule_array,
    char          * function,
    CCAI          * data_in_length,
    unsigned char * data_in,
    CCAI          * data_out_length,
    unsigned char * data_out
)
{
    printf("**debug** Running sample generic UDX call function: FUNC2\n");
    if (*data_out_length >= 12) {
        memcpy(data_out,"FUNC 2 IS OK",12);
        *data_out_length = 12;
        *return_code = 0;
        *reason_code = 0;
    }
    else {
        *return_code = 8;
        *reason_code = 72;  // output size to small
    }
}

Sample code can also be found in the installed CC ACSP server subdirectory sample file name jccauxcl.c.