PKCS 11 Provider across multiple Java versions

Advertisements

In order to interact with a smart card or a token device for authentication or signature purpose the PKCS#11 is one of the standards to do it. Java provides the SunPKCS11 Provider, which is a kind of wrapper that we can use to interact with native libraries that handles the communication with such kind of devices. Basically we install a library (dll, so, or dylib files) in the operative system, which is usually provided by the device vendor, and the through the SunPKCS11 provider we indicate the library file, the PKCS11 provider is in charge of the interacting with the device for us.

Therefore, Java has an out of the box support for interacting with those devices. However, in Java 9 some changes were included on how we can initialize the SunPKCS11Provider and those changes are not compatible any more with the previous versions of Java.

SunPKCS11 in Java 8 and previous

The following are the steps to create and configure the SunPKCS11 instance in Java 8 and previous versions.

import sun.security.pkcs11.SunPKCS11;

String configName = "/opt/bar/cfg/pkcs11.cfg";
Provider p = new SunPKCS11(configName);
Security.addProvider(p);

Basically, given a file /opt/bar/cfg/pkcs11.cfg with the configurations of that provider (library file, name, etc). We were able to initialize and configure the SunPKCS11 in only one line, using the constructor. Finally the new provider is added to the Security context.

SunPKCS11 in Java 9 and later

The following are the steps to create and configure the SunPKCS11 instance in Java 9 or later versions.

import sun.security.pkcs11.SunPKCS11

String configName = "/opt/bar/cfg/pkcs11.cfg";
Provider p = Security.getProvider("SunPKCS11");
p = p.configure(configName);
Security.addProvider(p);

In this case, the SunPKCS11 class doesn’t have a constructor available to instantiate it, and the configuration is done in a separate step, through the method configure(String configFile). Finally the new provider instance (after the configuration) is added to the security context.

Getting an instance of SunPKCS11 is possible through the Security context, using the getProvider method. It returns an “empty” instance that works as a “template”, then we use the method configure method.

The reason the SunPKCS11 doesn’t have a public constructor is because of the module encapsulation added in Java 9. Because it belongs to the sun.* package, they are restricted by default to be access by code.

Advertisements

A cross version solution

As we can see, both approach have changed how to initialize and configure it. Both approaches are not compatible because the definition (constructor and methods) of both are different. If we have an application that might run on Java 8 and newer versions, how to achieve that.

The solution is using the Java Reflection API.

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Provider;
import java.security.Security;

private static final String SUN_PKCS11_CLASSNAME = "sun.security.pkcs11.SunPKCS11";
private static final String SUN_PKCS11_PROVIDER_NAME = "SunPKCS11";

try {
  int javaVersion = JavaVersionUtil.getVersion();
  if(javaVersion >= 9){
    Provider prototype = Security.getProvider(SUN_PKCS11_PROVIDER_NAME);
    Class<?> sunPkcs11ProviderClass = Class.forName(SUN_PKCS11_CLASSNAME);
    Method configureMethod = sunPkcs11ProviderClass.getMethod("configure", String.class);
    return (Provider) configureMethod.invoke(prototype, configFile);
  } else {
    Class<?> sunPkcs11ProviderClass = Class.forName(SUN_PKCS11_CLASSNAME);
    Constructor<?> constructor = sunPkcs11ProviderClass.getConstructor(String.class);
    return (Provider) constructor.newInstance(configFile);
  }
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException 
            | IllegalArgumentException | InvocationTargetException 
            | NoSuchMethodException | SecurityException ex) {
     //Handle any error, log message or throw an exception
}

It is basically, detecting in runtime the version of Java running, and depending on its value it will access to the different methods of the SunPKCS11 class on each version. The constructor with configures it in Java 8 and before, or the “configure” method in Java 9 or later.

Find the Version of Java in Runtime

There is not rocket science on this part, it just checks the value on the system property java.version .

public class JavaVersionUtil {
    public static int getVersion() {
        String version = System.getProperty("java.version");
        if(version.startsWith("1.")) {
            version = version.substring(2, 3);
        } else {
            int dot = version.indexOf(".");
            if(dot != -1) { version = version.substring(0, dot); }
        } return Integer.parseInt(version);
    }
}

Enabling the crypto modules and packages

From Java 9 to 15, those packages could be still accessed, but after Java 16 they are not public by default, therefore we need to add some module clauses to accessed. That could be done adding the following lines to the Manifest.MF file of our jar

Add-Exports jdk.crypto.cryptoki/sun.security.pkcs11 jdk.crypto.cryptoki/sun.security.pkcs11.wrapper java.base/sun.security.action java.base/sun.security.rsa
Add-Opens java.base/java.security java.base/sun.security.util

I do that with maven using the maven-jar-plugin

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <version>3.0.2</version>
  <configuration>
    <archive>
      <manifest>
        .....
      </manifest>
      <manifestEntries>
        ....
        <Add-Exports>jdk.crypto.cryptoki/sun.security.pkcs11 jdk.crypto.cryptoki/sun.security.pkcs11.wrapper java.base/sun.security.action java.base/sun.security.rsa</Add-Exports>
        <Add-Opens>java.base/java.security java.base/sun.security.util</Add-Opens>                                   
      </manifestEntries>
    </archive>
  </configuration>
</plugin>

Conclusion

Hopefully this code will help other people dealing with the same situation where they need to support different versions of Java (for example Java desktop apps) and wants to provider a cross version solution.

References

Advertisements

Leave a Reply

Your email address will not be published. Required fields are marked *