Some test text!

menu
search

Digitally sign PDF files in Kotlin

Sample Kotlin code to use PDFTron SDK's high-level digital signature API for digitally signing and/or certifying PDF files. Learn more about our PDF Digital Signature Library.

Get StartedSamplesDownload

To run this sample, get started with a free trial of PDFTron SDK.

//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2019 by PDFTron Systems Inc. All Rights Reserved.
// Consult legal.txt regarding legal and license information.
//---------------------------------------------------------------------------------------

package com.pdftron.android.pdfnetsdksamples.samples

import com.pdftron.android.pdfnetsdksamples.OutputListener
import com.pdftron.android.pdfnetsdksamples.PDFNetSample
import com.pdftron.android.pdfnetsdksamples.R
import com.pdftron.android.pdfnetsdksamples.util.Utils
import com.pdftron.common.PDFNetException
import com.pdftron.pdf.*
import com.pdftron.pdf.annots.SignatureWidget
import com.pdftron.pdf.annots.TextWidget
import com.pdftron.sdf.SDFDoc
import java.util.*

//----------------------------------------------------------------------------------------------------------------------
// This sample demonstrates the basic usage of the high-level digital signatures API in PDFNet.
//
// The following steps reflect typical intended usage of the digital signatures API:
//
//	0.	Start with a PDF with or without form fields in it that one would like to lock (or, one can add a field, see (1)).
//	
//	1.	EITHER: 
//		(a) Call doc.createDigitalSignatureField, optionally providing a name. You receive a DigitalSignatureField.
//		-OR-
//		(b) If you didn't just create the digital signature field that you want to sign/certify, find the existing one within the 
//		document by using PDFDoc.DigitalSignatureFieldIterator or by using PDFDoc.getField to get it by its fully qualified name.
//	
//	2.	Create a signature widget annotation, and pass the DigitalSignatureField that you just created or found. 
//		If you want it to be visible, provide a Rect argument with a non-zero width or height, and don't set the
//		NoView and Hidden flags. [Optionally, add an appearance to the annotation when you wish to sign/certify.]
//		
//	[3. (OPTIONAL) Add digital signature restrictions to the document using the field modification permissions (SetFieldPermissions) 
//		or document modification permissions functions (SetDocumentPermissions) of DigitalSignatureField. These features disallow 
//		certain types of changes to be made to the document without invalidating the cryptographic digital signature's hash once it
//		is signed.]
//		
//	4. 	Call either CertifyOnNextSave or SignOnNextSave. There are three overloads for each one (six total):
//		a.	Taking a PKCS #12 keyfile path and its password
//		b.	Taking a buffer containing a PKCS #12 private keyfile and its password
//		c.	Taking a unique identifier of a signature handler registered with the PDFDoc. This overload is to be used
//			in the following fashion: 
//			i)		Extend and implement a new SignatureHandler. The SignatureHandler will be used to add or 
//					validate/check a digital signature.
//			ii)		Create an instance of the implemented SignatureHandler and register it with PDFDoc with 
//					pdfdoc.addSignatureHandler(). The method returns a SignatureHandlerId.
//			iii)	Call SignOnNextSaveWithCustomHandler/CertifyOnNextSaveWithCustomHandler with the SignatureHandlerId.
//		NOTE: It is only possible to sign/certify one signature per call to the Save function.
//	
//	5.	Call pdfdoc.save(). This will also create the digital signature dictionary and write a cryptographic hash to it.
//		IMPORTANT: If there are already signed/certified digital signature(s) in the document, you must save incrementally
//		so as to not invalidate the other signature's('s) cryptographic hashes. 
//
// Additional processing can be done before document is signed. For example, UseSignatureHandler() returns an instance
// of SDF dictionary which represents the signature dictionary (or the /V entry of the form field). This can be used to
// add additional information to the signature dictionary (e.g. Name, Reason, Location, etc.).
//
// Although the steps above describes extending the SignatureHandler class, this sample demonstrates the use of
// StdSignatureHandler (a built-in SignatureHandler in PDFNet) to sign a PDF file.
//----------------------------------------------------------------------------------------------------------------------

class DigitalSignaturesTest : PDFNetSample() {
    init {
        setTitle(R.string.sample_digitalsignatures_title)
        setDescription(R.string.sample_digitalsignatures_description)

        // After proper setup (eg. Spongy Castle libs installed,
        // MySignatureHandler.createSignature() returns a valid buffer), please comment line below
        // to enable sample.
        // If you are using the full library, you do not need to use the custom signature handler.
        // PDFDoc has a standard signature handler that can be used instead. Check the code below
        // for more info.
        //DisableRun();
    }

    override fun run(outputListener: OutputListener?) {
        super.run(outputListener)
        mOutputListener = outputListener
        mFileList.clear()
        printHeader(outputListener!!)
        // Initialize PDFNet

        var result = true

        //////////////////// TEST 0:
        /* Create an approval signature field that we can sign after certifying.
		(Must be done before calling CertifyOnNextSave/SignOnNextSave/WithCustomHandler.) */
        try {
            val doc = PDFDoc(Utils.getAssetTempFile(PDFNetSample.INPUT_PATH + "tiger.pdf")!!.absolutePath)
            val approval_signature_field = doc.createDigitalSignatureField("PDFTronApprovalSig")
            val widgetAnnotApproval = SignatureWidget.create(doc, Rect(300.0, 300.0, 500.0, 200.0), approval_signature_field)
            val page1 = doc.getPage(1)
            page1.annotPushBack(widgetAnnotApproval)
            doc.save(Utils.createExternalFile("tiger_withApprovalField_output.pdf", mFileList).absolutePath, SDFDoc.SaveMode.REMOVE_UNUSED, null)
        } catch (e: Exception) {
            System.err.println(e.message)
            e.printStackTrace(System.err)
            result = false
        }

        //////////////////// TEST 1: certify a PDF.
        try {
            certifyPDF(Utils.getAssetTempFile(PDFNetSample.INPUT_PATH + "tiger_withApprovalField.pdf")!!.absolutePath,
                    "PDFTronCertificationSig",
                    Utils.getAssetTempFile(PDFNetSample.INPUT_PATH + "pdftron.pfx")!!.absolutePath,
                    "password",
                    Utils.getAssetTempFile(PDFNetSample.INPUT_PATH + "pdftron.bmp")!!.absolutePath,
                    Utils.createExternalFile("tiger_withApprovalField_certified_output.pdf", mFileList).absolutePath)
            printSignaturesInfo(Utils.createExternalFile("tiger_withApprovalField_certified_output.pdf", mFileList).absolutePath)
        } catch (e: Exception) {
            System.err.println(e.message)
            e.printStackTrace(System.err)
            result = false
        }

        //////////////////// TEST 2: sign a PDF with a certification and an unsigned signature field in it.
        try {
            signPDF(Utils.getAssetTempFile(PDFNetSample.INPUT_PATH + "tiger_withApprovalField_certified.pdf")!!.absolutePath,
                    "PDFTronApprovalSig",
                    Utils.getAssetTempFile(PDFNetSample.INPUT_PATH + "pdftron.pfx")!!.absolutePath,
                    "password",
                    Utils.getAssetTempFile(PDFNetSample.INPUT_PATH + "signature.jpg")!!.absolutePath,
                    Utils.createExternalFile("tiger_withApprovalField_certified_approved_output.pdf", mFileList).absolutePath)
            printSignaturesInfo(Utils.createExternalFile("tiger_withApprovalField_certified_approved_output.pdf", mFileList).absolutePath)
        } catch (e: Exception) {
            System.err.println(e.message)
            e.printStackTrace(System.err)
            result = false
        }

        //////////////////// TEST 3: Clear a certification from a document that is certified and has two approval signatures.
        try {
            clearSignature(Utils.getAssetTempFile(PDFNetSample.INPUT_PATH + "tiger_withApprovalField_certified_approved.pdf")!!.absolutePath,
                    "PDFTronCertificationSig",
                    Utils.createExternalFile("tiger_withApprovalField_certified_approved_certcleared_output.pdf", mFileList).absolutePath)
            printSignaturesInfo(Utils.createExternalFile("tiger_withApprovalField_certified_approved_certcleared_output.pdf", mFileList).absolutePath)
        } catch (e: Exception) {
            System.err.println(e.message)
            e.printStackTrace(System.err)
            result = false
        }

        //////////////////// End of tests. ////////////////////

        if (result) {
            mOutputListener!!.println("Tests successful.\n==========")
        } else {
            mOutputListener!!.println("Tests FAILED!!!\n==========")
        }

        for (file in mFileList) {
            addToFileList(file)
        }
        printFooter(outputListener)
    }

    companion object {

        private var mOutputListener: OutputListener? = null

        private val mFileList = ArrayList<String>()

        @Throws(PDFNetException::class)
        fun certifyPDF(in_docpath: String,
                in_cert_field_name: String,
                in_private_key_file_path: String,
                in_keyfile_password: String,
                in_appearance_image_path: String,
                in_outpath: String) {
            mOutputListener!!.println("================================================================================")
            mOutputListener!!.println("Certifying PDF document")

            // Open an existing PDF
            val doc = PDFDoc(in_docpath)

            if (doc.hasSignatures()) {
                mOutputListener!!.println("PDFDoc has signatures")
            } else {
                mOutputListener!!.println("PDFDoc has no signatures")
            }

            val page1 = doc.getPage(1)

            // Create a text field that we can lock using the field permissions feature.
            val annot1 = TextWidget.create(doc, Rect(50.0, 550.0, 350.0, 600.0), "asdf_test_field")
            page1.annotPushBack(annot1)

            /* Create a new signature form field in the PDFDoc. The name argument is optional;
		leaving it empty causes it to be auto-generated. However, you may need the name for later.
		Acrobat doesn't show digsigfield in side panel if it's without a widget. Using a
		Rect with 0 width and 0 height, or setting the NoPrint/Invisible flags makes it invisible. */
            val certification_sig_field = doc.createDigitalSignatureField(in_cert_field_name)
            val widgetAnnot = SignatureWidget.create(doc, Rect(0.0, 100.0, 200.0, 150.0), certification_sig_field)
            page1.annotPushBack(widgetAnnot)

            // (OPTIONAL) Add an appearance.

            // Widget AP from image
            val img = Image.create(doc, in_appearance_image_path)
            widgetAnnot.createSignatureAppearance(img)
            // End of optional appearance-adding code.

            // Add permissions. Lock the random text field.
            mOutputListener!!.println("Adding document permissions.")
            certification_sig_field.documentPermissions = DigitalSignatureField.DocumentPermissions.e_annotating_formfilling_signing_allowed
            mOutputListener!!.println("Adding field permissions.")
            val fields_to_lock = arrayOf("asdf_test_field")
            certification_sig_field.setFieldPermissions(DigitalSignatureField.FieldPermissions.e_include, fields_to_lock)

            certification_sig_field.certifyOnNextSave(in_private_key_file_path, in_keyfile_password)

            ///// (OPTIONAL) Add more information to the signature dictionary.
            certification_sig_field.location = "Vancouver, BC"
            certification_sig_field.reason = "Document certification."
            certification_sig_field.contactInfo = "www.pdftron.com"
            ///// End of optional sig info code.

            // Save the PDFDoc. Once the method below is called, PDFNet will also sign the document using the information provided.
            doc.save(in_outpath, SDFDoc.SaveMode.NO_FLAGS, null)

            mOutputListener!!.println("================================================================================")
        }

        @Throws(PDFNetException::class)
        fun signPDF(in_docpath: String,
                in_approval_field_name: String,
                in_private_key_file_path: String,
                in_keyfile_password: String,
                in_appearance_img_path: String,
                in_outpath: String) {
            mOutputListener!!.println("================================================================================")
            mOutputListener!!.println("Signing PDF document")

            // Open an existing PDF
            val doc = PDFDoc(in_docpath)

            // Sign the approval signature.
            val found_approval_field = doc.getField(in_approval_field_name)
            val found_approval_signature_digsig_field = DigitalSignatureField(found_approval_field)
            val img = Image.create(doc, in_appearance_img_path)
            val found_approval_signature_widget = SignatureWidget(found_approval_field.sdfObj)
            found_approval_signature_widget.createSignatureAppearance(img)

            found_approval_signature_digsig_field.signOnNextSave(in_private_key_file_path, in_keyfile_password)

            doc.save(in_outpath, SDFDoc.SaveMode.INCREMENTAL, null)

            mOutputListener!!.println("================================================================================")
        }

        @Throws(PDFNetException::class)
        fun clearSignature(in_docpath: String,
                in_digsig_field_name: String,
                in_outpath: String) {
            mOutputListener!!.println("================================================================================")
            mOutputListener!!.println("Clearing certification signature")

            val doc = PDFDoc(in_docpath)

            val digsig = DigitalSignatureField(doc.getField(in_digsig_field_name))

            mOutputListener!!.println("Clearing signature: $in_digsig_field_name")
            digsig.clearSignature()

            if (!digsig.hasCryptographicSignature()) {
                mOutputListener!!.println("Cryptographic signature cleared properly.")
            }

            // Save incrementally so as to not invalidate other signatures' hashes from previous saves.
            doc.save(in_outpath, SDFDoc.SaveMode.INCREMENTAL, null)

            mOutputListener!!.println("================================================================================")
        }

        @Throws(PDFNetException::class)
        fun printSignaturesInfo(in_docpath: String) {
            mOutputListener!!.println("================================================================================")
            mOutputListener!!.println("Reading and printing digital signature information")

            val doc = PDFDoc(in_docpath)
            if (!doc.hasSignatures()) {
                mOutputListener!!.println("Doc has no signatures.")
                mOutputListener!!.println("================================================================================")
                return
            } else {
                mOutputListener!!.println("Doc has signatures.")
            }

            val fitr = doc.fieldIterator
            while (fitr.hasNext()) {
                val current = fitr.next()!!
                if (current.isLockedByDigitalSignature()) {
                    mOutputListener!!.println("==========\nField locked by a digital signature")
                } else {
                    mOutputListener!!.println("==========\nField not locked by a digital signature")
                }

                mOutputListener!!.println("Field name: " + current.getName())
                mOutputListener!!.println("==========")
            }

            mOutputListener!!.println("====================\nNow iterating over digital signatures only.\n====================")

            val digsig_fitr = doc.digitalSignatureFieldIterator
            while (digsig_fitr.hasNext()) {
                val current = digsig_fitr.next()!!
                mOutputListener!!.println("==========")
                mOutputListener!!.println("Field name of digital signature: " + Field(current.getSDFObj()).name)

                if (!current.hasCryptographicSignature()) {
                    mOutputListener!!.println("Either digital signature field lacks a digital signature dictionary, " +
                            "or digital signature dictionary lacks a cryptographic hash entry. " +
                            "Digital signature field is not presently considered signed.\n" +
                            "==========")
                    continue
                }

                val cert_count = current.getCertCount()
                mOutputListener!!.println("Cert count: $cert_count")
                for (i in 0 until cert_count) {
                    val cert = current.getCert(i)
                    mOutputListener!!.println("Cert #" + i + " size: " + cert.size)
                }

                val subfilter = current.getSubFilter()

                mOutputListener!!.println("Subfilter type: " + subfilter.ordinal)

                if (subfilter != DigitalSignatureField.SubFilterType.e_ETSI_RFC3161) {
                    mOutputListener!!.println("Signature's signer: " + current.getSignatureName())

                    val signing_time = current.getSigningTime()
                    if (signing_time.isValid) {
                        mOutputListener!!.println("Signing day: " + signing_time.day)
                    }

                    mOutputListener!!.println("Location: " + current.getLocation())
                    mOutputListener!!.println("Reason: " + current.getReason())
                    mOutputListener!!.println("Contact info: " + current.getContactInfo())
                } else {
                    mOutputListener!!.println("SubFilter == e_ETSI_RFC3161 (DocTimeStamp; no signing info)")
                }

                if (current.hasVisibleAppearance()) {
                    mOutputListener!!.println("Visible")
                } else {
                    mOutputListener!!.println("Not visible")
                }

                val digsig_doc_perms = current.getDocumentPermissions()
                val locked_fields = current.getLockedFields()
                for (it in locked_fields) {
                    mOutputListener!!.println("This digital signature locks a field named: $it")
                }

                when (digsig_doc_perms) {
                    DigitalSignatureField.DocumentPermissions.e_no_changes_allowed -> mOutputListener!!.println("No changes to the document can be made without invalidating this digital signature.")
                    DigitalSignatureField.DocumentPermissions.e_formfilling_signing_allowed -> mOutputListener!!.println("Page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.")
                    DigitalSignatureField.DocumentPermissions.e_annotating_formfilling_signing_allowed -> mOutputListener!!.println("Annotating, page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.")
                    DigitalSignatureField.DocumentPermissions.e_unrestricted -> mOutputListener!!.println("Document not restricted by this digital signature.")
                    else -> {
                        System.err.println("Unrecognized digital signature document permission level.")
                        assert(false)
                    }
                }
                mOutputListener!!.println("==========")
            }

            mOutputListener!!.println("================================================================================")
        }
    }

}
close

Free Trial

Get unlimited trial usage of PDFTron SDK to bring accurate, reliable, and fast document processing capabilities to any application or workflow.

Select a platform to get started with your free trial.

Unlimited usage. No email address required.

PDFTron Receives USD$71 Million Growth Investment Led By Silversmith Capital Partners

Learn More
close