Skip to main content
Skip table of contents

PAdES Signing High Level API

Intro

Our march toward improving the digital signature experience continues. Our previous iText 8.0.2 release saw the addition of the PdfPadesSigner class to enable convenient application of PAdES baseline levels. In our latest iText 8.0.3 release we have added PadesTwoPhaseSigningHelper (Java/.NET); a helper class enabling fast and easy two-step signing.

The four PAdES baseline digital signing profiles represent four increasingly complex verifications. Each advancement builds incrementally on the prior standard, and requires additional attributes to be added. Previously, advancing up the different levels of a PAdES baseline signature using our PdfSigner class was an arduous incremental process. For each level the user would have to instantiate a PdfSigner object, passing in the required attributes, saving the document with the digital signature container, then to reload the document and start the process again to achieve the next level.

Within the PdfPadesSigner class, individual methods for each PAdES baseline signing level accept the appropriate parameters for the required attributes to achieve that desired PAdES signing level without the incremental process. Additionally, we have added an easy high level method to add a new timestamp to existing PAdES LTA signatures.

The PadesTwoPhaseSigningHelper class enables the user to more easily preform a two-step signing workflow; preparing the document with a signature container, then actually signing the document using the appropriate corresponding PAdES baseline profile.

PAdES Levels

PAdES Levels

PAdES-B-B level

This is a basic signature. It remains valid as long as the certificate used to sign it is also valid (i.e. not expired or revoked).

PAdES-B-T level

This signature is an extension of the basic signature. It includes a cryptographic timestamp token to prove that the signed document existed at a given point in time.

PAdES-B-LT level

This signature builds on the previous one by incorporating all materials required to validate the signed document. This typically includes signing certificates, timestamp certificates, and revocation data (like CRL and OCSP responses). This makes it possible to validate the signed document using the contents of the file itself.

PAdES-B-LTA level

This signature provides long-term availability and integrity of validation material. It builds on the previous level by adding a cryptographic timestamp token to the document itself and the validation material (also called a “document timestamp”).

This establishes evidence that the validation material existed at that point in time, letting a signature validator determine that no certificates were revoked or expired at the time the signature was created.

In theory, it’s possible to periodically add further document timestamp tokens. This lets a document signature remain valid long after initial certificates–and even signing algorithms–have expired or were deemed insecure.

In the code sample below, we show how to create a PAdES level b signature, with the PadesTwoPhaseSigningHelper class.

Java
JAVA
public void twoPhaseSigningExample(String inFile, String outFile) throws IOException, GeneralSecurityException {
        String certPath = "src/main/resources/release/testcert.pfx";
        String password = "password";
        String digestAlgorithmn = DigestAlgorithms.SHA384;
        
        PdfReader reader = new PdfReader(inFile);
        FileOutputStream fos = new FileOutputStream(outFile)
        PadesTwoPhaseSigningHelper signingHelper = new PadesTwoPhaseSigningHelper();
        
        SignerProperties properties = new SignerProperties();
        properties.setFieldName("signatureField");

        BouncyCastleProvider providerBC = new BouncyCastleProvider();
        Security.addProvider(providerBC);
        KeyStore ks = KeyStore.getInstance("pkcs12");
        InputStream fis = Files.newInputStream(Paths.get(certPath));
        ks.load(fis, password.toCharArray());
        String alias = ks.aliases().nextElement();
        Certificate[] chain = ks.getCertificateChain(alias);
        PrivateKey pk = (PrivateKey) ks.getKey(alias, password.toCharArray());
        TSAClientBouncyCastle tsa = new TSAClientBouncyCastle("https://rfc3161.ai.moda");
        
        IExternalSignature externalSignature = new PrivateKeySignature(pk, digestAlgorithm, provider); 
        CMSContainer signatureContainer = signingHelper.createCMSContainerWithoutSignature(chain, digestAlgorithmn, reader, fos, signerProperties)
        
        signingHelper.signCMSContainerWithBaselineBProfile(externalSignature, reader, fos, "signatureField", signatureContainer  )
    }

API Links:

PdfPadesSigner API links for each PAdES baseline signing level:

In the code sample below, we show how you can create a Level B and Level LTA signature using PdfPadesSigner.

Code Samples

Java
JAVA
import com.itextpdf.commons.bouncycastle.operator.AbstractOperatorCreationException;
import com.itextpdf.commons.bouncycastle.pkcs.AbstractPKCSException;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.signatures.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;

public class PadesSigningExample {
    public static final String BASE_URI = "src/main/resources/release/";
    public static final String CERTS_URI=BASE_URI+"certs/";
    public static final String PASSWORD="testpassphrase";

    public static void main(String[] args) throws Exception {
        var m = new PadesSigningExample();
        String resource_uri = BASE_URI,
                inputFile = resource_uri + "input.pdf",
                outputFile = resource_uri + "out.pdf";
//                 m.baselineBProfileExample(inputFile, outputFile);
                 m.baselineLtaProfileExample(inputFile, outputFile);
    }

    public void baselineBProfileExample(String inFile, String outFile) throws IOException, GeneralSecurityException {
        String certPath = "src/main/resources/release/testcert.pfx";
        String password = "password";

        PdfPadesSigner padesSigner = new PdfPadesSigner(new PdfReader(inFile),new FileOutputStream(outFile));
        SignerProperties properties = new SignerProperties();
        properties.setFieldName("signatureField");

        BouncyCastleProvider providerBC = new BouncyCastleProvider();
        Security.addProvider(providerBC);
        KeyStore ks = KeyStore.getInstance("pkcs12");
        InputStream fis = Files.newInputStream(Paths.get(certPath));
        ks.load(fis, password.toCharArray());
        String alias = ks.aliases().nextElement();
        Certificate[] chain = ks.getCertificateChain(alias);
        PrivateKey pk = (PrivateKey) ks.getKey(alias, password.toCharArray());
        TSAClientBouncyCastle tsa = new TSAClientBouncyCastle("https://rfc3161.ai.moda");

       padesSigner.signWithBaselineLTAProfile(properties,chain,pk,tsa);
    }

    void baselineLtaProfileExample(String inFile, String outFile) throws IOException, GeneralSecurityException, AbstractOperatorCreationException, AbstractPKCSException {
        String signCertFileName = CERTS_URI+ "signCertRsa01.pem";
        String caCertFileName = CERTS_URI+"rootRsa.pem";

        BouncyCastleProvider providerBC = new BouncyCastleProvider();
        Security.addProvider(providerBC);

        Certificate[] signRsaChain = PemFileHelper.readFirstChain(signCertFileName);
        PrivateKey signRsaPrivateKey = PemFileHelper.readFirstKey(signCertFileName, PASSWORD.toCharArray());
        X509Certificate caCert = (X509Certificate) PemFileHelper.readFirstChain(caCertFileName)[0];
        PrivateKey caPrivateKey = PemFileHelper.readFirstKey(caCertFileName, PASSWORD.toCharArray());
        var pkSig = new PrivateKeySignature(signRsaPrivateKey, DigestAlgorithms.SHA256,BouncyCastleProvider.PROVIDER_NAME);

        PdfPadesSigner padesSigner = new PdfPadesSigner(new PdfReader(inFile),new FileOutputStream(outFile));
        SignerProperties signerProperties = new SignerProperties();

        ICrlClient crlClient = new TestCrlClient().addBuilderForCertIssuer(caCert, caPrivateKey);
        TestOcspClient ocspClient = new TestOcspClient().addBuilderForCertIssuer(caCert, caPrivateKey);
        TSAClientBouncyCastle tsa = new TSAClientBouncyCastle("https://rfc3161.ai.moda");

        padesSigner.setCrlClient(crlClient)
                .setOcspClient(ocspClient);

        padesSigner.signWithBaselineLTAProfile(signerProperties,signRsaChain,pkSig,tsa);
    }
C#
C#
using iText.Bouncycastleconnector;
using iText.Commons.Bouncycastle;
using iText.Commons.Bouncycastle.Cert;
using iText.Commons.Bouncycastle.Crypto;
using iText.Commons.Bouncycastle.Openssl;
using iText.Commons.Utils;
using iText.Kernel.Pdf;
using iText.Signatures;
using itext8_project.s81866;
using Org.BouncyCastle.Pkcs;

namespace itext8_project.ReleaseExample.itext802;

public class PadesSigningExample{
    public const String URI = "../../../resources/ReleaseExample/";
    public const String CERTS_URI = URI + "certs/";
    public const String PASSWORD = "testpassphrase";
    
    
    public void BaselineBProfileExample(String inFile, String outFile){
        String certPath = CERTS_URI+"certs/signCertRsa01.pem";

        PdfPadesSigner padesSigner = new PdfPadesSigner(new PdfReader(inFile), new FileStream(outFile, FileMode.Create));
        SignerProperties properties = new SignerProperties();
        properties.SetFieldName("signatureField");

        var chain = PemFileHelper.ReadFirstChain(certPath);
        var privateKey = PemFileHelper.ReadFirstKey(certPath, PASSWORD.ToCharArray());
        var pkSig = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256);

        TSAClientBouncyCastle tsa = new TSAClientBouncyCastle("https://rfc3161.ai.moda");

        // padesSigner.SignWithBaselineBProfile(properties, chain, pkSig);
        // padesSigner.SignWithBaselineLTAProfile(properties,chain,pkSig,tsa);
    }

    public void BaseLineLtaExample(String inFile, String outFile){
        String srcFileName = URI + "helloWorldDoc.pdf";
        String signCertFileName = CERTS_URI + "signCertRsa01.pem";
        String caCertFileName = CERTS_URI +"rootRsa.pem";
        
        PdfPadesSigner padesSigner = new PdfPadesSigner(new PdfReader(inFile), new FileStream(outFile, FileMode.Create));
        SignerProperties properties = new SignerProperties();
        properties.SetFieldName("signatureField");
        
        IX509Certificate[] signRsaChain = PemFileHelper.ReadFirstChain(signCertFileName);
        IPrivateKey signRsaPrivateKey = PemFileHelper.ReadFirstKey(signCertFileName, PASSWORD.ToCharArray());
        IX509Certificate caCert = (IX509Certificate)PemFileHelper.ReadFirstChain(caCertFileName)[0];
        IPrivateKey caPrivateKey = PemFileHelper.ReadFirstKey(caCertFileName, PASSWORD.ToCharArray());
        
        SignerProperties signerProperties = new SignerProperties();
        TSAClientBouncyCastle tsa = new TSAClientBouncyCastle("https://rfc3161.ai.moda");
        
        ICrlClient crlClient = new TestCrlClient().AddBuilderForCertIssuer(caCert, caPrivateKey);
        TestOcspClient ocspClient = new TestOcspClient().AddBuilderForCertIssuer(caCert, caPrivateKey);
        
        padesSigner.SetOcspClient(ocspClient)
            .SetCrlClient(crlClient);
        
        var privateKey = PemFileHelper.ReadFirstKey(signCertFileName, PASSWORD.ToCharArray());
        var pkSig = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256);
        
        padesSigner.SignWithBaselineLTAProfile(signerProperties, signRsaChain, pkSig, tsa);
    }

    class PemFileHelper{
        private static readonly IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.GetFactory();

        private PemFileHelper(){
        }

        public static IX509Certificate[] ReadFirstChain(String pemFileName){
            return ReadCertificates(pemFileName).ToArray(new IX509Certificate[0]);
        }

        public static IPrivateKey ReadFirstKey(String pemFileName, char[] keyPass){
            return ReadPrivateKey(pemFileName, keyPass);
        }

        public static List<IX509Certificate> InitStore(String pemFileName){
            IX509Certificate[] chain = ReadFirstChain(pemFileName);
            return chain.Length > 0 ? new List<IX509Certificate>{chain[0]} : chain.ToList();
        }

        private static IList<IX509Certificate> ReadCertificates(String pemFileName){
            using (TextReader file = new StreamReader(pemFileName)){
                IPemReader parser = FACTORY.CreatePEMParser(file, null);
                Object readObject = parser.ReadObject();
                IList<IX509Certificate> certificates = new List<IX509Certificate>();
                while (readObject != null){
                    if (readObject is IX509Certificate){
                        certificates.Add((IX509Certificate) readObject);
                    }

                    readObject = parser.ReadObject();
                }

                return certificates;
            }
        }

        private static IPrivateKey ReadPrivateKey(String pemFileName, char[] keyPass){
            using (TextReader file = new StreamReader(pemFileName)){
                IPemReader parser = FACTORY.CreatePEMParser(file, keyPass);
                Object readObject = parser.ReadObject();
                while (!(readObject is IPrivateKey) && readObject != null){
                    readObject = parser.ReadObject();
                }

                return (IPrivateKey) readObject;
            }
        }
    }
}

JavaScript errors detected

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

If this problem persists, please contact our support.