An IExternalSignature implementation for signing via PKCS#11 with the Entrust Signing Automation Service
This example was written to show how to use iText and the Entrust Signing Automation Service for PKCS#11 signing of PDF documents. The process for iText integration is described in the manual provided by Entrust, but here we’ll run through the basics relating to this example.
Having followed the instructions of the Entrust SAS User Guide, the SAS PKCS#11 driver will be installed in C:\Program Files\Entrust\SigningAutomationClient\P11SigningClient64.dll
on Windows for example. For reference, the setup on Linux is entirely analogous, it suffices to replace the path to P11SigningClient64.dll
with the path to libp11signingclient64.so
.
Consequentially the PKCS#11 configuration and instantiation will look like this:
JAVA
class TestSignSimple {
public final static File RESULT_FOLDER = new File("target/test-outputs", "signature");
@BeforeAll
public static void setUpBeforeClass() throws Exception {
RESULT_FOLDER.mkdirs();
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
}
@Test
void test() throws IOException, GeneralSecurityException {
config = "--name = Entrust\n"
+ "library = c:\\Program Files\\Entrust\\SigningClient\\P11SigningClient64.dll\n"
+ "slot = 1\n";
alias = null;
pin = "1234".toCharArray();
result = new File(RESULT_FOLDER, "circles-pkcs11-signed-simple-entrust-sas.pdf");
tsaClient = new TSAClientBouncyCastle("http://timestamp.entrust.net/TSS/RFC3161sha2TS");
testSignSimple();
}
protected void testSignSimple() throws IOException, GeneralSecurityException {
Pkcs11Signature signature = (config.startsWith("--") ? new Pkcs11Signature(config) : new Pkcs11Signature(new File(config)))
.select(alias, pin).setHashAlgorithm("SHA256");
try ( InputStream resource = getClass().getResourceAsStream("/circles.pdf");
PdfReader pdfReader = new PdfReader(resource);
OutputStream resultStream = new FileOutputStream(result) ) {
PdfSigner pdfSigner = new PdfSigner(pdfReader, resultStream, new StampingProperties().useAppendMode());
IExternalDigest externalDigest = new BouncyCastleDigest();
pdfSigner.signDetached(externalDigest , signature, signature.getChain(), null, null, tsaClient, 0, CryptoStandard.CMS);
}
}
protected String config;
protected String alias;
protected char[] pin;
protected File result;
protected ITSAClient tsaClient = null;
}
C#
class TestSignSimple
{
[Test]
public void TestPkcs11SignSimple()
{
string testFileName = @"..\..\..\resources\circles.pdf";
using (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"))
using (PdfReader pdfReader = new PdfReader(testFileName))
using (FileStream result = File.Create("circles-pkcs11-signed-simple.pdf"))
{
PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().UseAppendMode());
ITSAClient tsaClient = new TSAClientBouncyCastle("http://timestamp.entrust.net/TSS/RFC3161sha2TS");
pdfSigner.SignDetached(signature, signature.GetChain(), null, null, tsaClient, 0, CryptoStandard.CMS);
}
}
}
}
It should be noted that for this example the IExternalSignature
implementations in Java and .NET are very different. The Java implementation is built upon the Java Sun PKCS#11 provider which is well integrated into the Java JCA / JCE crypto architecture. See https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#Config for details on config file documentation.
The .NET implementation on the other hand is built upon the Pkcs11Interop package which can be retrieved via NuGet, https://www.nuget.org/packages/Pkcs11Interop/. This is a "Managed .NET wrapper for unmanaged PKCS#11 libraries", and so is not part of the official .NET crypto architecture. It is available under the terms of the Apache License, Version 2.0, see https://pkcs11interop.net/.
JAVA
package mkl.itext.signing.pkcs11;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.Signature;
import java.security.cert.Certificate;
import java.util.Enumeration;
import com.itextpdf.signatures.DigestAlgorithms;
import com.itextpdf.signatures.IExternalSignature;
/**
* @author mkl
*/
public class Pkcs11Signature implements IExternalSignature {
/** The alias. */
String alias;
/** The private key object. */
PrivateKey pk;
/** The certificate chain. */
Certificate[] chain;
/** The hash algorithm. */
String hashAlgorithm;
/** The encryption algorithm (obtained from the private key) */
String encryptionAlgorithm;
/** The security provider */
final Provider provider;
public Pkcs11Signature(File pkcs11configFile) {
Provider p = Security.getProvider("SunPKCS11");
provider = p.configure(pkcs11configFile.getAbsolutePath());
Security.addProvider(provider);
}
public Pkcs11Signature(String pkcs11config) {
Provider p = Security.getProvider("SunPKCS11");
if (!pkcs11config.startsWith("--"))
pkcs11config = "--" + pkcs11config;
provider = p.configure(pkcs11config);
Security.addProvider(provider);
}
public Pkcs11Signature(Provider pkcs11provider) {
provider = pkcs11provider;
Security.addProvider(provider);
}
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;
}
public String getAlias() {
return alias;
}
public Certificate[] getChain() {
return chain;
}
@Override
public String getEncryptionAlgorithm() {
return encryptionAlgorithm;
}
@Override
public String getHashAlgorithm() {
return hashAlgorithm;
}
public Pkcs11Signature setHashAlgorithm(String hashAlgorithm) {
this.hashAlgorithm = DigestAlgorithms.getDigest(DigestAlgorithms.getAllowedDigest(hashAlgorithm));
return this;
}
@Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
String algorithm = hashAlgorithm + "with" + encryptionAlgorithm;
Signature sig = Signature.getInstance(algorithm, provider);
sig.initSign(pk);
sig.update(message);
return sig.sign();
}
}
C#
using iText.Signatures;
using Net.Pkcs11Interop.Common;
using Net.Pkcs11Interop.HighLevelAPI;
using Net.Pkcs11Interop.HighLevelAPI.Factories;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
namespace itext.signing.pkcs11_Net
{
public class Pkcs11Signature : IExternalSignature, IDisposable
{
IPkcs11Library pkcs11Library;
ISlot slot;
ISession session;
IObjectHandle privateKeyHandle;
string alias;
X509Certificate[] chain;
string encryptionAlgorithm;
string hashAlgorithm;
public Pkcs11Signature (string libraryPath, ulong slotId)
{
Pkcs11InteropFactories factories = new Pkcs11InteropFactories();
pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, libraryPath, AppType.MultiThreaded);
slot = pkcs11Library.GetSlotList(SlotsType.WithOrWithoutTokenPresent).Find(slot => slot.SlotId == slotId);
}
public Pkcs11Signature Select(string alias, string certLabel, string pin)
{
List<CKA> pkAttributeKeys = new List<CKA>();
pkAttributeKeys.Add(CKA.CKA_KEY_TYPE);
pkAttributeKeys.Add(CKA.CKA_LABEL);
pkAttributeKeys.Add(CKA.CKA_ID);
List<CKA> certAttributeKeys = new List<CKA>();
certAttributeKeys.Add(CKA.CKA_VALUE);
certAttributeKeys.Add(CKA.CKA_LABEL);
CloseSession();
session = slot.OpenSession(SessionType.ReadWrite);
session.Login(CKU.CKU_USER, pin);
ObjectAttributeFactory objectAttributeFactory = new ObjectAttributeFactory();
List<IObjectAttribute> attributes = new List<IObjectAttribute>();
attributes.Add(objectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY));
List<IObjectHandle> keys = session.FindAllObjects(attributes);
bool found = false;
foreach (IObjectHandle key in keys)
{
List<IObjectAttribute> keyAttributes = session.GetAttributeValue(key, pkAttributeKeys);
ulong type = keyAttributes[0].GetValueAsUlong();
string encryptionAlgorithm;
switch (type)
{
case (ulong)CKK.CKK_RSA:
encryptionAlgorithm = "RSA";
break;
case (ulong)CKK.CKK_DSA:
encryptionAlgorithm = "DSA";
break;
case (ulong)CKK.CKK_ECDSA:
encryptionAlgorithm = "ECDSA";
break;
default:
continue;
}
string thisAlias = keyAttributes[1].GetValueAsString();
if (thisAlias == null || thisAlias.Length == 0)
thisAlias = keyAttributes[2].GetValueAsString();
if (alias != null && !alias.Equals(thisAlias))
continue;
attributes.Clear();
attributes.Add(objectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_CERTIFICATE));
attributes.Add(objectAttributeFactory.Create(CKA.CKA_CERTIFICATE_TYPE, CKC.CKC_X_509));
if (certLabel == null && thisAlias != null && thisAlias.Length > 0)
certLabel = thisAlias;
if (certLabel != null)
attributes.Add(objectAttributeFactory.Create(CKA.CKA_LABEL, certLabel));
List<IObjectHandle> certificates = session.FindAllObjects(attributes);
if (certificates.Count != 1)
continue;
IObjectHandle certificate = certificates[0];
List<IObjectAttribute> certificateAttributes = session.GetAttributeValue(certificate, certAttributeKeys);
X509Certificate x509Certificate =
new X509Certificate(X509CertificateStructure.GetInstance(certificateAttributes[0].GetValueAsByteArray()));
List<X509Certificate> x509Certificates = new List<X509Certificate>();
x509Certificates.Add(x509Certificate);
attributes.Clear();
attributes.Add(objectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_CERTIFICATE));
attributes.Add(objectAttributeFactory.Create(CKA.CKA_CERTIFICATE_TYPE, CKC.CKC_X_509));
List<IObjectHandle> otherCertificates = session.FindAllObjects(attributes);
foreach (IObjectHandle otherCertificate in otherCertificates)
{
if (!certificate.ObjectId.Equals(otherCertificate.ObjectId))
{
certificateAttributes = session.GetAttributeValue(otherCertificate, certAttributeKeys);
X509Certificate otherX509Certificate =
new X509Certificate(X509CertificateStructure.GetInstance(certificateAttributes[0].GetValueAsByteArray()));
x509Certificates.Add(otherX509Certificate);
}
}
found = true;
this.alias = thisAlias;
this.encryptionAlgorithm = encryptionAlgorithm;
this.privateKeyHandle = key;
this.chain = x509Certificates.ToArray();
break;
}
if (!found)
{
this.alias = null;
this.encryptionAlgorithm = null;
this.privateKeyHandle = null;
this.chain = null;
}
return this;
}
public void Dispose()
{
CloseSession();
slot = null;
pkcs11Library?.Dispose();
}
private void CloseSession()
{
if (session != null)
{
try
{
session.Dispose();
}
finally
{
privateKeyHandle = null;
session = null;
}
}
}
public X509Certificate[] GetChain()
{
return chain;
}
public string GetEncryptionAlgorithm()
{
return encryptionAlgorithm;
}
public string GetHashAlgorithm()
{
return hashAlgorithm;
}
public Pkcs11Signature SetHashAlgorithm(String hashAlgorithm)
{
this.hashAlgorithm = DigestAlgorithms.GetDigest(DigestAlgorithms.GetAllowedDigest(hashAlgorithm));
return this;
}
public byte[] Sign(byte[] message)
{
MechanismFactory mechanismFactory = new MechanismFactory();
IMechanism mechanism;
switch(encryptionAlgorithm)
{
case "DSA":
switch(hashAlgorithm)
{
case "SHA1":
mechanism = mechanismFactory.Create(CKM.CKM_DSA_SHA1);
break;
case "SHA224":
mechanism = mechanismFactory.Create(CKM.CKM_DSA_SHA224);
break;
case "SHA256":
mechanism = mechanismFactory.Create(CKM.CKM_DSA_SHA256);
break;
case "SHA384":
mechanism = mechanismFactory.Create(CKM.CKM_DSA_SHA384);
break;
case "SHA512":
mechanism = mechanismFactory.Create(CKM.CKM_DSA_SHA512);
break;
default:
throw new ArgumentException("Not supported: " + hashAlgorithm + "with" + encryptionAlgorithm);
}
break;
case "ECDSA":
switch (hashAlgorithm)
{
case "SHA1":
mechanism = mechanismFactory.Create(CKM.CKM_ECDSA_SHA1);
break;
case "SHA224":
mechanism = mechanismFactory.Create(CKM.CKM_ECDSA_SHA224);
break;
case "SHA256":
mechanism = mechanismFactory.Create(CKM.CKM_ECDSA_SHA256);
break;
case "SHA384":
mechanism = mechanismFactory.Create(CKM.CKM_ECDSA_SHA384);
break;
case "SHA512":
mechanism = mechanismFactory.Create(CKM.CKM_ECDSA_SHA512);
break;
default:
throw new ArgumentException("Not supported: " + hashAlgorithm + "with" + encryptionAlgorithm);
}
break;
case "RSA":
switch (hashAlgorithm)
{
case "SHA1":
mechanism = mechanismFactory.Create(CKM.CKM_SHA1_RSA_PKCS);
break;
case "SHA224":
mechanism = mechanismFactory.Create(CKM.CKM_SHA224_RSA_PKCS);
break;
case "SHA256":
mechanism = mechanismFactory.Create(CKM.CKM_SHA256_RSA_PKCS);
break;
case "SHA384":
mechanism = mechanismFactory.Create(CKM.CKM_SHA384_RSA_PKCS);
break;
case "SHA512":
mechanism = mechanismFactory.Create(CKM.CKM_SHA512_RSA_PKCS);
break;
default:
throw new ArgumentException("Not supported: " + hashAlgorithm + "with" + encryptionAlgorithm);
}
break;
default:
throw new ArgumentException("Not supported: " + hashAlgorithm + "with" + encryptionAlgorithm);
}
return session.Sign(mechanism, privateKeyHandle, message);
}
}
}