Skip to main content
Skip table of contents

Part III - PKCS#11

The PKCS#11 standard defines a platform-independent API for accessing cryptographic tokens. If a device manufacturer or a service operator provides a PKCS#11 driver, they allow you to use that device or service from different platforms with the same functionality and the same key material.

In this part we focus on signing via PKCS#11; first we illuminate how to access PKCS#11 drivers from Java and .Net, and then we use those ways of access with a number of example devices.

How to Address PKCS#11 Drivers in Java

There are two main ways to address tokens via PKCS#11 drivers in Java: Via a security provider that abstracts away the PKCS#11 API and via a thin PKCS#11 wrapper that maps the PKCS#11 API to Java classes and methods.

Using a PKCS#11 Security Provider

If you address a PKCS#11 driver via a Java security provider, you can of course use the PrivateKeySignature class included in the Java distribution as described before. To later be able to focus on actual differences, we will use a different IExternalSignature implementation, one that provides convenience methods for selecting key and certificate by their alias.

This IExternalSignature implementation Pkcs11Signature accepts or initializes a PKCS#11 security provider in its constructor; if it initializes one itself, it uses the SunPKCS11 provider. It allows key selection using this method:

JAVA
public Pkcs11Signature select(String alias, char[] pin) throws GeneralSecurityException, IOException {

    KeyStore ks = KeyStore.getInstance("PKCS11", provider);
    ks.load(null, pin);

    boolean found = false;
    Enumeration <String> aliases = ks.aliases();

    while (aliases.hasMoreElements()) {
        String thisAlias = aliases.nextElement();
        if (alias == null || alias.equals(thisAlias)) {
            PrivateKey thisPk = (PrivateKey) ks.getKey(thisAlias, pin);
            if (thisPk == null)
                continue;

            Certificate[] thisChain = ks.getCertificateChain(thisAlias);
            if (thisChain == null)
                continue;

            found = true;
            pk = thisPk;
            chain = thisChain;
            this.alias = thisAlias;
            break;
        }
    }

    if (found) {
        String algorithm = pk.getAlgorithm();
        encryptionAlgorithm = "EC".equals(algorithm) ? "ECDSA" : algorithm;
    } else {
        pk = null;
        chain = null;
        this.alias = null;
        encryptionAlgorithm = null;
    }

    return this;
}

Then it allows signature algorithm specification (if it is not yet clear from the key itself) with this method:

JAVA
public Pkcs11Signature with(String algorithm, AlgorithmParameterSpec paramSpec) {
    fullSignatureAlgorithmName = algorithm;
    this.fullSignatureAlgorithmParamSpec = paramSpec;
    return this;
}

Eventually it signs using:

JAVA
public byte[] sign(byte[] message) throws GeneralSecurityException {
    String algorithm = fullSignatureAlgorithmName != null ? fullSignatureAlgorithmName :
        digestAlgorithmName + "with" + signatureAlgorithmName;
    Signature sig = Signature.getInstance(algorithm, provider);
    sig.initSign(pk);
    if (fullSignatureAlgorithmParamSpec != null)
        sig.setParameter(fullSignatureAlgorithmParamSpec);
    sig.update(message);
    return sig.sign();
}

(From Pkcs11Signature.java; full code here on GitHub.)

Thus, if you have a SunPKCS11 configuration as string in config, the desired alias in alias, the pin in pin, and the desired hash algorithm in hash, you initialize a Pkcs11Signature like this:

JAVA
Pkcs11Signature signature = new Pkcs11Signature(config)
	.select(alias, pin).setDigestAlgorithmName(hash);

In the case of RSASSA-PSS, you additionally have to specify signature algorithm details like this:

JAVA
Pkcs11Signature signature = new Pkcs11Signature(config)
    .select(alias, pin).setDigestAlgorithmName("SHA256")
    .with("SHA256withRSASSA-PSS", new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1));

(From BaseSignSimple.java; full code here on GitHub)

Similarly we use a custom IExternalSignatureContainer implementation for signing via a PKCS#11 security provider, to be able to focus on details specific to the use case in later examples.

This custom implementation Pkcs11SignatureContainer actually is very similar to the UtimacoJceSignatureContainer above. Just like in that case we cannot use the PrivateKeySignatureContainerBC  as the way BouncyCastle normalizes signature algorithm names there interferes with the proper execution of code in the PKCS11 providers.

Merely the constructors and the keystore retrieval are PKCS11 provider specific, similar to the Pkcs11Signature in this section. You can find the code here on GitHub.

Using the SunPKCS11 Security Provider

The PKCS#11 provider included in the Java API, SunPKCS11, is the obvious choice for accessing PKCS#11 via a security provider as it’s included in the common Java runtime distributions.

In older Java versions, e.g. Java 8, you could instantiate and register SunPKCS11 for a specific PKCS#11 driver like this:

JAVA
Provider providerPKCS11 = new SunPKCS11(config);
Security.addProvider(providerPKCS11);

In later Java versions, e.g. Java 11, you instead do it like this:

JAVA
Provider p = Security.getProvider("SunPKCS11");
Provider providerPKCS11 = p.configure(config);
Security.addProvider(providerPKCS11);

Here config is the path and name of a configuration file. The exact contents of such a configuration file depend on your PKCS#11 driver and device or service. The general form is like this:

name = Utimaco library = d:\Program Files\Utimaco\CryptoServer\Lib\cs_pkcs11_R2.dll slot = 0

Alternatively config may contain that configuration as a plain String prefixed by “–”:

config = "--name = Utimaco\nlibrary = "    + "d:/Program Files/Utimaco/CryptoServer/Lib/cs_pkcs11_R2.dll\n"    + "slot = 0\n";

You can find details in the Java documentation.

Using the IAIK PKCS#11 Security Provider

An alternative PKCS#11 provider has been implemented at the Graz University of Technology and can be retrieved here. It is an independent implementation and may be better fitted to your use case. In particular it has slightly different expectations on how keys and associated certificates are tagged in a PKCS#11 API implementation.

You instantiate and register the IAIK security provider like this:

JAVA
Properties properties = new Properties();
properties.setProperty(KEY, VALUE);
...

IAIKPkcs11 provider = new IAIKPkcs11(properties);
Security.addProvider(provider);

The set of properties to set depend on your PKCS#11 driver and device or service. It might amount to something like this:

JAVA
properties.setProperty("PKCS11_NATIVE_MODULE", "c:/Program Files (x86)/Personal/bin64/personal64.dll");
properties.setProperty("SLOT_ID", "1");

Using the IAIK PKCS#11 Wrapper

If you prefer to access the PKCS#11 driver using a Java API matching the PKCS#11 API, you can do so using a thin PKCS#11 wrapper.

Doing so will quite likely take more code to identify the matching private keys and certificates than to actually sign. If you use security providers instead, this job mostly is done under the hood. But security providers have to make certain assumptions how to recognize such matches, and some PKCS#11 drivers or the devices underneath present their keys and certificates differently. So in some cases you eventually will have to use a solution based on a PKCS#11 wrapper.

The Graz University of Technology provides such a wrapper here, which they actually use themselves in their PKCS#11 Java security provider.

If you use this wrapper library to access PKCS#11 drivers, you can utilize code similar to that in the Pkcs11WrapperKeyAndCertificate class (here on GitHub) to open a session and reference the private key and certificate you want to work with. Depending on peculiarities of the PKCS#11 drivers and devices you use you might have to tweak it a bit. If you need the code to work for a single driver and device type only, your code may be considerably shorter and simpler.

Having selected a private key and a certificate entry, though, using them in a IExternalSignature implementation is easy:

JAVA
public byte[] sign(byte[] message) throws GeneralSecurityException {
    // Set mechanismId to a mechanism ID from PKCS11Constants.CKM_*
    // to match the signature algorithm you want to use, e.g.
    long mechanismId = PKCS11Constants.CKM_SHA256_RSA_PKCS;
    Mechanism signatureMechanism = Mechanism.get(mechanismId);

    try {
        session.signInit(signatureMechanism, privateKey);
        return session.sign(message);
    } catch (TokenException e) {
        throw new GeneralSecurityException(e);
    }
}

(From Pkcs11WrapperSignature; full code here on GitHub.)

In an IExternalSignatureContainer implementation you can use such a key and certificate utilizing the CMS signature container creation features of some security library, such as BouncyCastle which we have used already in earlier examples:

JAVA
public byte[] sign(InputStream data) throws GeneralSecurityException {
    try {
        CMSTypedData msg = new CMSTypedDataInputStream(data);
        X509CertificateHolder signCert = new X509CertificateHolder(chain[0].getEncoded());

        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        gen.addSignerInfoGenerator(
            new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder()
                    .setProvider("BC").build()).build(buildContentSigner(signatureAlgorithm), signCert));
        gen.addCertificates(new JcaCertStore(Arrays.asList(chain)));

        CMSSignedData sigData = gen.generate(msg, false);
        return sigData.getEncoded();
    } catch (IOException | OperatorCreationException | CMSException | TokenException e) {
        throw new GeneralSecurityException(e);
    }
}

(From Pkcs11WrapperSignatureContainer; full code here on GitHub.)

Here buildContentSigner returns a custom ContentSigner implementation using the key and certificate references from the PKCS#11 wrapper:

JAVA
public ContentSigner buildContentSigner(String signatureAlgorithm) throws TokenException {
    AlgorithmIdentifier signAlgorithmIdentifier =
        new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithm);
    Long mechanism = MECHANISM_BY_ALGORITHM_LOWER.get(signatureAlgorithm.toLowerCase());
    if (mechanism == null)
        throw new IllegalArgumentException(String.format("No applicable mechanism for '%s'", signatureAlgorithm));

    session.signInit(Mechanism.get(mechanism), privateKey);
    return new ContentSigner() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        @Override
        public byte[] getSignature() {
            try {
                byte[] signature = session.sign(baos.toByteArray());
                return signature;
            } catch (TokenException e) {
                throw new RuntimeOperatorException(e.getMessage(), e);
            }
        }

        @Override
        public OutputStream getOutputStream() {
            return baos;
        }

        @Override
        public AlgorithmIdentifier getAlgorithmIdentifier() {
            return signAlgorithmIdentifier;
        }
    };
}

(From Pkcs11WrapperKeyAndCertificate; full code here on GitHub.)

How to Address PKCS#11 Drivers in .NET

.NET does not offer a “native” PKCS#11 integration. Apparently Microsoft counts on security device manufacturers and security service operators to make platform specific CryptoAPI or CNG providers available.

Many manufacturers and operators do offer such a provider but many others don’t. Furthermore, in some cases the providers do not allow to access the same key material as the PKCS#11 drivers. Thus, a number of independent software packages turned up that do allow accessing PKCS#11 drivers from .NET. As an example we use Pkcs11Interop, a managed .NET wrapper for unmanaged PKCS#11 libraries, available via NuGet and documented here

Just like the IAIK PKCS#11 Wrapper for Java discussed above Pkcs11Interop offers a thin wrapper around the PKCS#11, in this case using .NET classes and methods. Consequently, just like in the Java case you may have to write more code to identify the matching private keys and certificates than to actually sign using them, at least if you try to make that not specific to some device or service.

Thus, you can use code like in the method Select of the example IExternalSignature implementation Pkcs11Signature (here on GitHub) to open a session and reference the private key and certificate you want to work with. Depending on peculiarities of the PKCS#11 drivers and devices you use you might have to tweak it a bit. If you need the code to work for a single driver and device type only, your code may be considerably shorter and simpler.

Also just like in the case above, the Sign method turns out to be simple:

C#
public byte[] Sign(byte[] message)
{
    MechanismFactory mechanismFactory = new MechanismFactory();
    // Set mechanism to a IMechanism implementation provided by the
    // mechanismFactory to match the signature algorithm you want to
    // use, e.g.
    IMechanism mechanism = mechanismFactory.Create(CKM.CKM_SHA256_RSA_PKCS);

    return session.Sign(mechanism, privateKeyHandle, message);
}

(From Pkcs11Signature; full code here on GitHub.)

Also, similar to the Java case above we can create an IExternalSignatureContainer implementation based on Pkcs11Interop and the CMS building of BouncyCastle or .NET core libraries themselves.

Specific Examples

In this section we’ll take a look at a number of devices and services with PKCS#11 drivers in respect to how to use them for signing with iText.

BeID, the Belgium Identity Card

You can easily sign PDFs using the Belgian ID card via PKCS#11. E.g. by initializing the Pkcs11Signature from above like this:

JAVA
config = "name = BeID\n" +
    "library = \"c:/Program Files (x86)/Belgium Identity Card/" +
    "FireFox Plugin Manifests/beid_ff_pkcs11_64.dll\"\n" +
    "slot = 0\n";
alias = "Signature";
pin = "1234".toCharArray();

Pkcs11Signature signature = new Pkcs11Signature(config).select(alias, pin).setDigestAlgorithmName("SHA256");

Of course you need to know where you installed your Belgium Identity Card software, which slot the card in question is in (usually only relevant if you have multiple card readers), and which PIN your card has.

For RSASSA-PSS signatures we additionally need to supply algorithm details:

JAVA
config = "name = BeID\n" +
    "library = \"c:/Program Files (x86)/Belgium Identity Card/" +
    "FireFox Plugin Manifests/beid_ff_pkcs11_64.dll\"\n" +
    "slot = 0\n";
alias = "Signature";
pin = "1234".toCharArray();

Pkcs11Signature signature = new Pkcs11Signature(config).select(alias, pin).setDigestAlgorithmName("SHA256")
    .with("SHA256withRSASSA-PSS", new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1));

If we need an IExternalSignatureContainer implementation, so we use Pkcs11SignatureContainer here and initialize it like this:

JAVA
config = "name = BeID\n" +
    "library = \"c:/Program Files (x86)/Belgium Identity Card/" +
    "FireFox Plugin Manifests/beid_ff_pkcs11_64.dll\"\n" +
    "slot = 0\n";
alias = "Signature";
pin = "1234".toCharArray();

Pkcs11SignatureContainer signature =
    new Pkcs11SignatureContainer(config, PdfName.Adbe_pkcs7_detached)
        .select(alias, pin)
        .with("SHA256withRSASSA-PSS", new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1));

Again you need to know where you installed your Belgium Identity Card software, which slot the card in question is in, and which PIN your card has.

D-Trust Qualified Signature Card

In contrast to the BeID which could be used without issue, using the D-Trust Qualified Signature Card requires clearing a number of hurdles.

First of all the Bundesdruckerei does not provide a free PKCS#11 driver. We obtained one for Windows by buying the Nexus Personal suite.

The next hurdle is due to the fact that the D-Trust card features two signature mechanisms, one with an advanced certificate and one with a qualified signature. The Nexus Personal driver exposes them as distinct slots. If you have initialized them with different PINs and keep experimenting with the wrong slot, you can brick that mechanism.

Once you’ve got your driver and slot sorted out, you might imagine you can apply the Pkcs11Signature class like this:

JAVA
config = "--name = DTrustOnNexus\nlibrary = " +
    "\"c:/Program Files (x86)/Personal/bin64/personal64.dll\"\n" +
    "slot = 1\n";
alias = null;
pin = "12345678".toCharArray();

Pkcs11Signature signature = new Pkcs11Signature(config).select(alias, pin).setHashAlgorithm("SHA256");

Unfortunately, though, this results in an invalid signature!

This is caused by a provider/driver incompatibility: The SunPKCS11 provider, when looking for keys and certificates on a PKCS11 device, iterates over the private keys and associates each of them with the certificate with the same ID. But the Nexus driver for D-Trust cards offers all certificates with the same ID and SunPKCS11 uses the one it retrieves first, by chance the root certificate, not the signer certificate. Thus, the private key and certificate do not cryptographically match, making the result signature invalid.

Fortunately we have an alternative provider, the IAIK PKCS#11 security provider:

JAVA
String alias = "Signaturzertifikat";
char[] pin = "12345678".toCharArray();

Properties properties = new Properties();
properties.setProperty("PKCS11_NATIVE_MODULE", "c:/Program Files (x86)/Personal/bin64/personal64.dll");
properties.setProperty("SLOT_ID", "1");

IAIKPkcs11 provider = new IAIKPkcs11(properties);
Security.addProvider(provider);

Pkcs11Signature signature = new Pkcs11Signature(provider).select(alias, pin).setHashAlgorithm("SHA256");

Using this signature instance we finally succeed. The IAIK provider does not only offer the pairing of the private key and the first certificate with the same ID like the SunPKCS11 provider but instead all pairings of the private key with such a certificate, with an alias derived from the label of the certificate. By selecting the pairing for the alias "Signaturzertifikat", therefore, the used private key and certificate do cryptographically match, making the result signature valid.

Unfortunately, though, the IAIK provider requires licensing, and if you don’t qualify for a free license (e.g. a research license), your project may not have the resources to afford a license.

Fortunately you can fall back on the IAIK PKCS#11 Wrapper which offers use under an Apache-style license. Thus, you can make use of the Pkcs11WrapperSignature from above initialized like this:

JAVA
String certLabel = "Signaturzertifikat";
char[] pin = "12345678".toCharArray();

Pkcs11WrapperSignature signature = new Pkcs11WrapperSignature("c:/Program Files (x86)/Personal/bin64/personal64.dll", 1)
    .select(null, certLabel, pin).setHashAlgorithm("SHA256");

and of the Pkcs11WrapperSignatureContainer initialized like this:

JAVA
String certLabel = "Signaturzertifikat";
char[] pin = "12345678".toCharArray();

Pkcs11WrapperSignatureContainer signature =
    new Pkcs11WrapperSignatureContainer("c:/Program Files (x86)/Personal/bin64/personal64.dll", 1)
        .select(null, certLabel, pin)
        .setSignatureAlgorithm("SHA256withRSA"); 

Also, if you want to create RSASSA-PSS signatures, simply request the signature algorithm like this instead

JAVA
.setSignatureAlgorithm("SHA256withRSAandMGF1");

Entrust Signing Automation Service

The Entrust Signing Automation Service is a Cloud-based digital signing service that can be plugged in to your document applications and workflows in order to automatically generate Entrust-issued digital seals for your organisation's documents. (Quoted from the Entrust web site.)

Many other Cloud-based digital signing services provide a custom web API to access their service. In contrast to that Entrust provides a PKCS#11 driver to access their service. Thus, you can simply use the Java Pkcs11Signature initialized like this:

JAVA
config = "--name = Entrust\nlibrary ="
  + "c:/Program Files/Entrust/SigningClient/P11SigningClient64.dll\n"
  + "slot = 1\n";
alias = null;
pin = "1234".toCharArray();

Pkcs11Signature signature = new Pkcs11Signature(config)
    .select(alias, pin).setHashAlgorithm("SHA256");

Similarly, the .NET Pkcs11Signature can be initialized like this:

C#
Pkcs11Signature signature = new Pkcs11Signature(
    @"c:\Program Files\Entrust\SigningClient\P11SigningClient64.dll",
    1)
  .Select(null,
      "CN=Entrust Limited,OU=ECS,O=Entrust Limited,L=Kanata,ST=Ontario,C=CA",
      "1234")
  .SetHashAlgorithm("SHA256")

This integration has also been discussed in this separate article.

SoftHSM

SoftHSM is an implementation of a cryptographic store accessible through a PKCS #11 interface. You can use it to explore PKCS #11 without having a Hardware Security Module. It is being developed as a part of the OpenDNSSEC project. (Quoted from the OpenDNSSEC web site.)

SoftHSM can be used to represent a generic PKCS#11 device and driver in test or presentation scenarios.

This emulated device can be used without issues for signing with iText, e.g. initializing a Pkcs11Signature like this:

JAVA
config = "--name = 171137967\n" +
    "library = d:/Program Files/SoftHSM2/lib/softhsm2-x64.dll\n" +
    "slot = 171137967\n";
alias = null;
pin = "5678".toCharArray();

Pkcs11Signature signature = new Pkcs11Signature(config).select(alias, pin).setHashAlgorithm("SHA256");

Similarly easily you can initialize a Pkcs11SignatureContainer, a Pkcs11WrapperSignature, or a Pkcs11WrapperSignatureContainer for signing with SoftHSM.

Utimaco HSMs

Utimaco is a world-leading manufacturer and specialized vendor of Hardware Security Modules. Utimaco offers fully functional HSM software simulators for download. The Utimaco HSM simulator enables evaluation, development and integration testing without purchase, delivery or installation of hardware. (Quoted from the Utimaco web site)

In addition to a PKCS#11 driver Utimaco also supports platform dependent providers (see part II in Signing With Custom JCA/JCE Security Providers (Java) and Signing With Custom CNG Integrations (.NET)). For a platform independent integration, though, one should use the PKCS#11 driver.

The following code has been tested using the Utimaco SecurityServer HSM simulator. By configuring the Utimaco PKCS#11 configuration (in particular the cs_pkcs11_R2.cfg configuration file) accordingly, the code should run with actual Utimaco HSMs.

You can easily sign PDFs using Utimaco HSMs card via PKCS#11. E.g. in Java by initializing the Pkcs11Signature from Using a PKCS#11 Security Provider like this:

JAVA
config = "name = Utimaco\nlibrary = " +
    "d:/Program Files/Utimaco/CryptoServer/Lib/cs_pkcs11_R2.dll\n" +
    "slot = 0\n";
alias = null;
pin = "5678".toCharArray();

Pkcs11Signature signature = new Pkcs11Signature(config).select(alias, pin).setDigestAlgorithmName("SHA256");

For RSASSA-PSS signatures it suffices to initialize the Pkcs11Signature differently:

JAVA
Pkcs11Signature signature = new Pkcs11Signature(config).select(alias, pin).setDigestAlgorithmName("SHA256")
    .with("SHA256withRSASSA-PSS", new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1));

With iText version 7 you still needed an IExternalSignatureContainer implementation for RSASSA-PSS signatures, you could use Pkcs11SignatureContainer here and initialize it like this:

JAVA
config = "name = Utimaco\nlibrary = " +
    "d:/Program Files/Utimaco/CryptoServer/Lib/cs_pkcs11_R2.dll\n" +
    "slot = 0\n";
alias = null;
pin = "1234".toCharArray();

Pkcs11SignatureContainer signature =
    new Pkcs11SignatureContainer(config, PdfName.Adbe_pkcs7_detached)
        .select(alias, pin)
        .with("SHA256withRSASSA-PSS", new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1));

In .NET you can initialize the Pkcs11Signature from "How to Address PKCS#11 Drivers in .NET" above like this:

C#
Pkcs11Signature signature = new Pkcs11Signature(
	@"d:\Program Files\Utimaco\CryptoServer\Lib\cs_pkcs11_R2.dll", 0)
		.Select(null, null, "5678")
		.SetHashAlgorithm("SHA256");

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.