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:
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:
public Pkcs11Signature with(String algorithm, AlgorithmParameterSpec paramSpec) {
fullSignatureAlgorithmName = algorithm;
this.fullSignatureAlgorithmParamSpec = paramSpec;
return this;
}
Eventually it signs using:
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:
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:
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:
Provider providerPKCS11 = new SunPKCS11(config);
Security.addProvider(providerPKCS11);
In later Java versions, e.g. Java 11, you instead do it like this:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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
.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:
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:
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:
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:
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:
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:
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:
Pkcs11Signature signature = new Pkcs11Signature(
@"d:\Program Files\Utimaco\CryptoServer\Lib\cs_pkcs11_R2.dll", 0)
.Select(null, null, "5678")
.SetHashAlgorithm("SHA256");