In contexts with higher security requirements the private keys usually are not available in local key stores but instead in external devices which are hardened against attempts to copy the private keys (e.g. smart cards or HSMs) or even in remote servers. To sign data with such private keys you have to use an API provided by the manufacturer of the device or the operator of the service respectively.
Such APIs may follow some standard. E.g.
- PKCS#11 drivers - the PKCS#11 standard defines a platform-independent API to cryptographic tokens.
- Java security providers / MS Windows CryptoAPI or CNG key storage providers - the Java and MS Windows platforms offer their specific security modules which are easy to access from Java and .NET respectively. These modules allow plugging in external devices and remote services via a provider architecture.
But these APIs may also be completely proprietary.
The manufacturer/operator actually might offer access via multiple APIs, e.g., they might offer both a platform independent PKCS#11 driver and platform specific providers. Be aware though, that this does not mean that you can freely switch back and forth between them. In particular individual keys or information associated with them might only be accessible via the API which was used to create or add them in the first place. So if you plan to implement an application on multiple platforms in parallel, you should use the same platform independent API in all implementations, not the (probably easier-to-use) respective platform specific ones.
In this part we take a look at the platform specific providers and proprietary APIs. The next part will focus on PKCS#11 drivers.
Signing With External Services (proprietary APIs)
In particular in the context of external signing services you often find proprietary web APIs. Some of them want the original data to sign as input, some want a hash of that data as-is, some want that hash packaged in a specific data structure. Some of them return naked signature bytes, some a full signature container. Some merely require a normal username-password authorization, some require a two-factor authorization. Etc.
Thus, one can hardly provide any general rules here. Merely that based on the signature structure returned by the API in question you need to implement either
IExternalSignatureContainer. And that you should study the documentation of the API to know exactly what to provide and what to expect in return.
As an example you might want to take a look at the technical note “Using iText 7 and AWS KMS to digitally sign a PDF document” which explains how to use the Key Management Service (KMS) of the Amazon Web Services (AWS) to sign PDFs.
Another example is the usage of Singapore's National Digital identity (NDI) project to create PDF signatures with iText. You can request example code and documentation here. (Beware, that example code is based on an earlier version of NDI; thus, it most likely won’t work out of the box with the current NDI.)
Signing With Custom JCA/JCE Security Providers (Java)
If a security token manufacturer or a signing service operator provides a Java security provider for signing, you’re essentially in the situation of section “Signing with Java JCA/JCE keys” of Part I except that you may have to do some configuration beforehand (please consult the documentation of the token or service).
Utimaco, for example, provides a Java security provider to access their HSMs. Using this provider one can sign PDFs as follows:
(Full code in test method
testSignSimpleGeneric in <https://git.itextsupport.com/projects/I7JS/repos/signing-examples/browse/jce-utimaco/src/test/java/com/itextpdf/signingexamples/jce/utimaco/TestSignSimple.java>)
Unfortunately the situation sometimes is more difficult because a custom Java Security Provider may require unusual parameters differing from those the
PrivateKeySignature class uses. In such a situation work-arounds are needed.
For example, the Utimaco JCE
CryptoServerProvider (at least in the version I have used) for RSASSA-PSS signatures expects the signature algorithm be given without any indication that the PSS scheme is used, e.g. as "SHA256withRSA" instead of "SHA256withRSASSA-PSS" or "SHA256withRSAandMGF1". It then uses the PSS scheme if one sets a
AlgorithmParameterSpec parameter. And in that
PSSParameterSpec it expects the hash algorithm to be given with a dash, e.g. "SHA-256" instead of "SHA256". The iText
PrivateKeySignature, though, uses "RSASSA-PSS" in the signature algorithm and "SHA256" in the PSS parameters. This way the CMS signature container parameters all are correct, merely the signing fails. As a work-around, therefore, we can use a
PrivateKeySignature and override its sign method like this:
(Full code in method
testSignSimpleGenericPss in <https://github.com/itext/i7js-signing-examples/blob/develop/jce-utimaco/src/test/java/com/itextpdf/signingexamples/jce/utimaco/TestSignSimple.java>)
In iText version 7 RSASSA-PSS was not supported using
IExternalSignature implementations, so an
IExternalSignatureContainer implementation was needed, for example using BouncyCastle CMS builder classes directly. Even here, though, the quirk of the Utimaco JCE crypto provider requires a work around to be used because the default BouncyCastle classes for this job use the signature algorithm name "SHA256WITHRSAANDMGF1" instead of the "SHA256WITHRSA" required by the Utimaco provider.
Thus, we create a custom
IExternalSignatureContainer implementation which replaces the BouncyCastle class doing that to and fro transformation with its own one that doesn’t. The BouncyCastle class in question is a ContentSigner generated by JcaContentSignerBuilder. So we build our own ContentSigner:
UtimacoJceSignatureContainer.java; full code at <https://git.itextsupport.com/projects/I7JS/repos/signing-examples/browse/jce-utimaco/src/main/java/com/itextpdf/signingexamples/jce/utimaco/UtimacoJceSignatureContainer.java>)
If you set the algorithm in this way, you can sign with RSASSA-PSS like this:
(Full code in test method
testSignSimpleUtimacoJceSignatureContainerRsaSsaPss in <https://git.itextsupport.com/projects/I7JS/repos/signing-examples/browse/jce-utimaco/src/test/java/com/itextpdf/signingexamples/jce/utimaco/TestSignSimple.java>)
Signing With Custom CNG Integrations (.NET)
If a security token manufacturer or a signing service operator provides a .NET CryptoAPI or CNG key storage provider for signing, you’re essentially in the situation of the “Signing with .NET CryptoAPI/CNG Keys” section of Part I except that you may have to do some configuration beforehand (please consult the documentation of the token or service).
Utimaco, for example, provides a CNG key storage provider to access their HSMs. Let’s assume you have associated a certificate in your personal certificate store with a Utimaco CNG key using "
Utimaco CNG Signing Test" as the subject common name.
X509Certificate2Signature class from part I one can then sign PDFs like this:
(Full test code in method
TestCngSignSimpleGeneric in <https://git.itextsupport.com/projects/I7NS/repos/samples/browse/itext/itext.publications/itext.publications.signing-examples.cng-utimaco-test/iText/SigningExamples/CngUtimaco/TestSignSimple.cs>)
Using the Utimaco provider with the
X509Certificate2SignatureContainer class from Part I you can sign PDFs like this:
(Full test code in method
TestCngSignSimpleGenericContainer in <https://git.itextsupport.com/projects/I7NS/repos/samples/browse/itext/itext.publications/itext.publications.signing-examples.cng-utimaco-test/iText/SigningExamples/CngUtimaco/TestSignSimple.cs>)