Skip to main content
Skip table of contents

Part I - Overview and Simple Cases

An Overview

The iText signing API is based on the PdfSigner class. This class takes care of the PDF-specific aspects of PDF signing.

Additionally you need a class that takes care of the cryptographic, not PDF-specific aspects of signing. As this part depends very much on your specific signing device, iText here offers an interface you can implement accordingly. Actually it offers you a choice of two interfaces:

  • IExternalSignature - implement this interface if your signature creation device returns a simple naked signature value and you want iText to build a full CMS signature container for it.

  • IExternalSignatureContainer - implement this interface if your signature creation device already returns a full CMS signature container, or if you are not content with how iText builds a CMS signature container for your simple signature values and you want to do that yourself.

In later parts of this article series you’ll find a number of implementations of these interfaces for a number of different types of signature creation devices.

Signing With IExternalSignature

When signing with an IExternalSignature implementation iText will build a CMS signature container around the naked signature value that the implementation returns. For this iText uses additional information:

  • An IExternalDigest instance - this object provides implementations of hashing algorithms. Usually one can use the implementations provided by iText, the class BouncyCastleDigest directly provides BouncyCastle hash algorithm implementations, and the class ProviderDigest provides hash algorithm implementations from a given JCA security provider.

  • A [X509]Certificate array - this is the chain of X509 certificates starting with the signer certificate. This signer certificate at least must be given, but adding more certificates helps validators to verify the signature.

  • (Optionally) a collection of ICrlClient instances and/or an IOcspClient instance - these objects can be used to embed revocation information into the signature. This is completely optional but can help validators to verify the signatures. In the case of PAdES baseline and extended profiles, though, it is wrong to add revocation information this early.

  • (Optionally) an ITSAClient instance - if given this object is used to add a digital timestamp to the signature. It is generally a good idea to add a digital timestamp. For common RFC 3161 timestamp servers you can use the iText implementation TSAClientBouncyCastle.

  • A CryptoStandard value - for CMS the signature is generated as a PKCS#7 Signatures as used in ISO 32000 (ISO 32000-1 / ISO 32000-2), for CADES it is generated as a PAdES signature (ETSI EN 319 142 / ISO 32000-2).

  • (Optionally) a SignaturePolicyIdentifier or SignaturePolicyInfo object - this object is embedded in the signature to indicate the policy under which the signature is created.

A short example template would look like this in Java:

JAVA
PdfReader reader = …;
OutputStream os = …;

Certificate[] chain = …;
IExternalSignature signature = …;
IExternalDigest digest = new BouncyCastleDigest();

PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
signer.signDetached(digest, signature, chain, null, null, null, 0, CryptoStandard.CMS);

And like this in C#/.NET:

C#
PdfReader reader = …;
Stream os = …;

X509Certificate[] chain = …;
IExternalSignature signature = …;
IExternalDigest digest = new BouncyCastleDigest();

PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
signer.SignDetached(digest, signature, chain, null, null, null, 0, CryptoStandard.CMS);

The difference is merely the capitalization of the signing method, the use of .NET Stream instead of Java OutputStream, and the use of .NET BouncyCastle X509Certificate instead of Java security Certificate.

Signing With IExternalSignatureContainer

When signing with an IExternalSignatureContainer implementation iText will merely embed the CMS signature container returned by that implementation. For this iText doesn’t need any additional information. It merely cannot sensibly guess the size of the container to eventually embed; thus, one has to provide a sensible estimate here.

A short example template would look like this in Java:

JAVA
pdfReader reader = …;
OutputStream os = …;

IExternalSignatureContainer signatureContainer = …;
int estimatedSize = 12000;

PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
pdfSigner.signExternalContainer(signatureContainer, estimatedSize);

And like this in C#/.NET:

C#
PdfReader reader = …;
Stream os = …;

IExternalSignatureContainer signatureContainer = …;
int estimatedSize = 12000;

PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
pdfSigner.SignExternalContainer(signatureContainer, estimatedSize);

The difference again merely is the capitalization of the signing method and the use of .NET Stream instead of Java OutputStream.

Which Interface to Use

As mentioned above, one criterion is whether your signature creation device returns naked signature bytes or signature containers. But even in the first case there are situations in which you may not want iText to build a signature container around the naked signature bytes but do so yourself. Such situations in particular arise if you need to use cryptographic algorithms not supported by iText or ensure a container profile differing from that generated by iText.

In such a situation you would implement IExternalSignatureContainer even though your signature creation device may return merely naked signature bytes, and that implementation would include code building a CMS signature container around those signature bytes. Often this would be done using BouncyCastle classes but other cryptography libraries can also be used; maybe other such libraries even have to be used whenever BouncyCastle itself introduces restrictions.

Signing with Java JCA/JCE keys

If you access your signature creation device using Java Cryptography Architecture (JCA) mechanisms, you have a PrivateKey instance to create a signature and one or more Certificate instances to identify the signer.

iText for Java already includes an IExternalSignature implementation for this use case, the PrivateKeySignature class. You merely need the private key, the desired hash algorithm, and the security provider of choice to create an instance, e.g.:

IExternalSignature signature = new PrivateKeySignature(pk, "SHA256", BouncyCastleProvider.PROVIDER_NAME);

iText does not include an IExternalSignatureContainer implementation for this use case but it is easy to create. For example a basic sign method based on BouncyCastle could look like this:

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

        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(contentSigner, signCert));
        gen.addCertificates(new JcaCertStore(Collections.singleton(signCert)));

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

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

The contentSigner herein is initialized in the constructor as 

contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey);

You would instantiate it like this:

IExternalSignatureContainer signature = new

    PrivateKeySignatureContainerBC("SHA512withRSAandMGF1", pk, cert,

        PdfName.Adbe_pkcs7_detached);

for a PrivateKey pk and a signer X509Certificate cert.

Signing with .NET BouncyCastle Keys

iText for .NET also includes an IExternalSignature implementation, also called PrivateKeySignature, but this implementation is based on the BouncyCastle ICipherParameters interface (e.g. the AsymmetricKeyParameter instances one can retrieve from a PKCS#12 file) instead of the .NET crypto API.

If you have the ICipherParameters of your private key and the desired hash algorithm, you instantiate this external signature implementation like this:

IExternalSignature signature = new PrivateKeySignature(params, "SHA384");


iText does not include an IExternalSignatureContainer implementation for this use case but it is easy to create. One essentially uses BouncyCastle mechanisms also for CMS signature container creation. A basic Sign method could look like this:

C#
public byte[] Sign(Stream data)
{
    CmsProcessable msg = new CmsProcessableInputStream(data);

    CmsSignedDataGenerator gen = new CmsSignedDataGenerator();
    SignerInfoGenerator signerInfoGenerator = new SignerInfoGeneratorBuilder()
        .WithSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator())
        .Build(new Asn1SignatureFactory(algorithm, key), chain[0]);
    gen.AddSignerInfoGenerator(signerInfoGenerator);

    IStore<X509Certificate> store = CollectionUtilities.CreateStore( new List<X509Certificate>(chain));
    gen.AddCertificates(store);

    CmsSignedData sigData = gen.Generate(msg, false);
    return sigData.GetEncoded();
}

(From PrivateKeySignatureContainer.cs; full code here on GitHub.)

Signing with .NET CryptoAPI/CNG Keys

More natural in the .NET world would be an implementation based on the old .NET CryptoAPI or the newer .NET CNG (Cryptography API: Next Generation). iText 5 still included the IExternalSignature implementation X509Certificate2Signature which used CryptoAPI private keys attached to a X509Certificate2 instance. This class can easily be ported to iText 7 and enhanced to also support CNG keys. The Sign method can simply look like this:

C#
public byte[] Sign(byte[] message)
{
    switch (signatureAlgorithmName)
    {
        case "RSA":
            return certificate.GetRSAPrivateKey().SignData(message, new HashAlgorithmName(digestAlgorithmName),
                UsePssForRsaSsa ? RSASignaturePadding.Pss : RSASignaturePadding.Pkcs1);
        case "DSA":
            return PlainToDer(certificate.GetDSAPrivateKey().SignData(message, new HashAlgorithmName(digestAlgorithmName)));
        case "ECDSA":
            return certificate.GetECDsaPrivateKey().SignData(message, new HashAlgorithmName(digestAlgorithmName));
        default:
            throw new ArgumentException("Unknown signature algorithm " + signatureAlgorithmName);
    }
}

(From X509Certificate2Signature.cs; full code here on GitHub.)

You would instantiate it like this:

IExternalSignature signature = new X509Certificate2Signature(certificate, "SHA384");

An analog IExternalSignatureContainer implementation can easily be created. A basic Sign method could look like this:

C#
public byte[] Sign(Stream data)
{
    ContentInfo content = new ContentInfo(StreamUtil.InputStreamToArray(data));
    SignedCms signedCms = new SignedCms(content, true);
    CmsSigner signer = new CmsSigner(certificate);

    customization?.Invoke(signer);

    signer.IncludeOption = X509IncludeOption.WholeChain;
    signedCms.ComputeSignature(signer);
    return signedCms.Encode();
}

(From X509Certificate2SignatureContainer.cs; full code here on GitHub)

Here certificate is the X509Certificate2 instance and customization is an Action<CmsSigner> allowing you to customize the signature creation process, e.g. by selecting a digest algorithm.

X509Certificate2SignatureContainer can be instantiated like this:

X509Certificate2SignatureContainer signature =

    new X509Certificate2SignatureContainer(certificate);

or like this (with digest algorithm customization):

X509Certificate2SignatureContainer signature =

    new X509Certificate2SignatureContainer(certificate, signer =>

{

    signer.DigestAlgorithm =

       Oid.FromFriendlyName("SHA512", OidGroup.HashAlgorithm);

});

Unfortunately the .NET CmsSigner class does not (yet) allow for selecting RSASSA-PSS when signing with a RSA key even though the CNG RSA key class does allow selecting RSASSA-PSS when creating raw signature values. For RSASSA-PSS CMS signatures, therefore, you have to construct the CMS container separately, e.g. using BouncyCastle:

Signing With Keys in PKCS#12 Files

If you have a private key and associated X509 certificate in a PKCS#12 file (in MS Windows contexts also known as PFX files), you can easily use the IExternalSignature and IExternalSignatureContainer implementations above as you can extract keys and certificates in the required forms.

In Java you can read the PKCS#12 file contents like this:

JAVA
KeyStore ks = KeyStore.getInstance("pkcs12", "SunJSSE");
ks.load(new FileInputStream(STORE_PATH), STORE_PASS);
PrivateKey pk = (PrivateKey) ks.getKey(ALIAS, KEY_PASS);
Certificate[] chain = ks.getCertificateChain(ALIAS);

If the certificates in the PKCS#12 file are X509 certificates, the elements of chain are X509Certificate instances and can simply be cast, e.g.:

X509Certificate signerCert = (X509Certificate) chain[0];

In .NET you can read the PKCS#12 file contents into BouncyCastle objects like this:

C#
Pkcs12Store pkcs12 = new Pkcs12Store(new FileStream(STORE_PATH, FileMode.Open, FileAccess.Read), STORE_PASS);
AsymmetricKeyParameter key = pkcs12.GetKey(ALIAS).Key;
X509CertificateEntry[] chainEntries = pkcs12.GetCertificateChain(ALIAS);
X509Certificate[] chain = new X509Certificate[chainEntries.Length];
for (int i = 0; i < chainEntries.Length; i++)
    chain[i] = chainEntries[i].Certificate;

In .NET you can read the PKCS#12 file contents into a X509Certificate2 like this (some using directives are included for clarification):

C#
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.X509;
using SystemCertificates = System.Security.Cryptography.X509Certificates;

...

SystemCertificates.X509Certificate2 certificate = new SystemCertificates.X509Certificate2(storePath, storePass);
X509Certificate bcCertificate = new X509Certificate(X509CertificateStructure.GetInstance(certificate.RawData));
X509Certificate[] chain = { bcCertificate };
JavaScript errors detected

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

If this problem persists, please contact our support.