Some test text!

Search
Hamburger Icon

Digitally sign PDF files in Swift

More languages

More languages
JavaScript
Java (Android)
C++
C#
C# (.NET Core)
Go
Java
Kotlin
Obj-C
JS (Node.js)
PHP
Python
Ruby
Swift
C# (UWP)
VB
C# (Xamarin)

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

Get Started Samples Download

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

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

//----------------------------------------------------------------------------------------------------------------------
// 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.
//----------------------------------------------------------------------------------------------------------------------

// To build and run this sample, please specify OpenSSL include & lib paths to the Makefile
//
// If OpenSSL is installed elsewhere, it may be necessary to add the path to the headers in the $(INCLUDE) variable as
// well as the location of either libcrypto.a or libcrypto.so/libcrypto.dylib.

// Note for iOS development: This code can be used to digitally sign PDFs in iOS devices. When using the code signing
// part of this code, it will be necessary to compile OpenSSL for iOS.

import PDFNet
import Foundation

// EXPERIMENTAL. Digital signature verification is undergoing active development, but currently does not support a number of features. If we are missing a feature that is important to you, or if you have files that do not act as expected, please contact us using one of the following forms: https://apryse.com/form/trial-support/
func VerifyAllAndPrint(_ in_docpath: String?, _ in_public_key_file_path: String?) -> Bool {
    let doc: PTPDFDoc = PTPDFDoc(filepath: in_docpath)
    print("==========")
    let opts: PTVerificationOptions = PTVerificationOptions(level: e_ptcompatibility_and_archiving)

    // Trust the public certificate we use for signing.
    let trusted_cert_file: PTMappedFile = PTMappedFile(filename: in_public_key_file_path)
    let file_sz = trusted_cert_file.fileSize()
    let file_reader: PTFilterReader = PTFilterReader(filter: trusted_cert_file)
    let trusted_cert_buf = file_reader.read(file_sz)
    opts.addTrustedCertificate(trusted_cert_buf)

    // Iterate over the signatures and verify all of them.
    let digsig_fitr: PTDigitalSignatureFieldIterator = doc.getDigitalSignatureFieldIterator()
    var verification_status = true
    while digsig_fitr.hasNext() {
        defer {
            digsig_fitr.next()
        }
        
        let curr: PTDigitalSignatureField = digsig_fitr.current()
        let result: PTVerificationResult = curr.verify(opts)
        if result.getVerificationStatus() {
            print("Signature verified, objnum: \(curr.getSDFObj().getNum())")
        } else {
            print("Signature verification failed, objnum: \(curr.getSDFObj().getNum())")
            verification_status = false
        }
        
        switch result.getSignersDigestAlgorithm() {
        case e_ptsha1:
            print("Digest algorithm: SHA-1")
        case e_ptsha256:
            print("Digest algorithm: SHA-256")
        case e_ptsha384:
            print("Digest algorithm: SHA-384")
        case e_ptsha512:
            print("Digest algorithm: SHA-512")
        case e_ptripemd160:
            print("Digest algorithm: RIPEMD-160")
        case e_ptunknown_digest_algorithm:
            print("Digest algorithm: unknown")
        default:
            let e = NSException(name: NSExceptionName("unrecognized digest algorithm"), reason: "unrecognized digest algorithm", userInfo: nil)
            e.raise()
        }
        
        print("Detailed verification result: ")
        switch result.getDocumentStatus() {
        case e_ptno_error:
            print("\tNo general error to report.")
        case e_ptcorrupt_file:
            print("\tSignatureHandler reported file corruption.")
        case e_ptunsigned:
            print("\tThe signature has not yet been cryptographically signed.")
        case e_ptbad_byteranges:
            print("\tSignatureHandler reports corruption in the ByteRanges in the digital signature.")
        case e_ptcorrupt_cryptographic_contents:
            print("\tSignatureHandler reports corruption in the Contents of the digital signature.")
        default:
            let e = NSException(name: NSExceptionName("unrecognized document status"), reason: "unrecognized document status", userInfo: nil)
            e.raise()
        }
        switch result.getDigestStatus() {
        case e_ptdigest_invalid:
            print("\tThe digest is incorrect.")
        case e_ptdigest_verified:
            print("\tThe digest is correct.")
        case e_ptdigest_verification_disabled:
            print("\tDigest verification has been disabled.")
        case e_ptweak_digest_algorithm_but_digest_verifiable:
            print("\tThe digest is correct, but the digest algorithm is weak and not secure.")
        case e_ptno_digest_status:
            print("\tNo digest status to report.")
        case e_ptunsupported_encoding:
            print("\tNo installed SignatureHandler was able to recognize the signature's encoding.")
        default:
            let e = NSException(name: NSExceptionName("unrecognized digest status"), reason: "unrecognized digest status", userInfo: nil)
            e.raise()
        }
        switch result.getTrustStatus() {
        case e_pttrust_verified:
            print("\tEstablished trust in signer successfully.")
        case e_ptuntrusted:
            print("\tTrust could not be established.")
        case e_pttrust_verification_disabled:
            print("\tTrust verification has been disabled.")
        case e_ptno_trust_status:
            print("\tNo trust status to report.")
        default:
            let e = NSException(name: NSExceptionName("unrecognized trust status"), reason: "unrecognized trust status", userInfo: nil)
            e.raise()
        }
        
        switch result.getPermissionsStatus() {
        case e_ptinvalidated_by_disallowed_changes:
            print("\tThe document has changes that are disallowed by the signature's permissions settings.")
        case e_pthas_allowed_changes:
            print("\tThe document has changes that are allowed by the signature's permissions settings.")
        case e_ptunmodified:
            print("\tThe document has not been modified since it was signed.")
        case e_ptpermissions_verification_disabled:
            print("\tPermissions verification has been disabled.")
        case e_ptno_permissions_status:
            print("\tNo permissions status to report.")
        default:
            let e = NSException(name: NSExceptionName("unrecognized modification permissions status"), reason: "unrecognized modification permissions status", userInfo: nil)
            e.raise()
        }
        
        let changes: [PTDisallowedChange] = result.getDisallowedChanges()
        for change in changes {
            print("\tDisallowed change: \(change.getTypeAsString() ?? ""), objnum: \(change.getObjNum())")
        }
        
        if result.hasTrust() {
            let trust_verification_result: PTTrustVerificationResult = result.getTrust()
            print(trust_verification_result.wasSuccessful() ? "Trust verified." : "Trust not verifiable.")
            print("Trust verification result string: \(trust_verification_result.getString() ?? "")")
            
            let tmp_time_t = trust_verification_result.getTimeOfTrustVerification()
            switch trust_verification_result.getTimeOfTrustVerificationEnum() {
            case e_ptcurrent:
                print(String(format: "Trust verification attempted with respect to current time (as epoch time): %lu", tmp_time_t))
            case e_ptsigning:
                print(String(format: "Trust verification attempted with respect to signing time (as epoch time): %lu", tmp_time_t))
            case e_pttimestamp:
                print(String(format: "Trust verification attempted with respect to secure embedded timestamp (as epoch time): %lu", tmp_time_t))
            default:
                let e = NSException(name: NSExceptionName("unrecognized time enum value"), reason: "unrecognized time enum value", userInfo: nil)
                e.raise()
            }
        } else {
            print("No detailed trust verification result available.")
        }
        
        print("==========")
    }
    
    return verification_status
}

func 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?) {
    
    print("================================================================================")
    print("Certifying PDF document")

    // Open an existing PDF
    let doc: PTPDFDoc = PTPDFDoc(filepath: in_docpath)

    print("PDFDoc has \(doc.hasSignatures() ? "signatures" : "no signatures")")

    let page1: PTPage = doc.getPage(1)
    
    // Create a text field that we can lock using the field permissions feature.
    let annot1 = PTTextWidget.create(doc, pos: PTPDFRect(x1: 50, y1: 550, x2: 350, y2: 600), field_name: "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. */
    let certification_sig_field: PTDigitalSignatureField = doc.createDigitalSignatureField(in_cert_field_name)
    let widgetAnnot: PTSignatureWidget = PTSignatureWidget.create(withDigitalSignatureField: doc, pos: PTPDFRect(x1: 0, y1: 100, x2: 200, y2: 150), field: certification_sig_field)
    page1.annotPushBack(widgetAnnot)

    // (OPTIONAL) Add an appearance to the signature field.
    let img = PTImage.create(withFile: doc.getSDFDoc(), filename: in_appearance_image_path, encoder_hints: PTObj())
    widgetAnnot.createSignatureAppearance(img)

    // Prepare the document locking permission level. It will be applied upon document certification.
    print("Adding document permissions.")
    certification_sig_field.setDocumentPermissions(e_ptannotating_formfilling_signing_allowed)
    
    // Prepare to lock the text field that we created earlier.
    print("Adding field permissions.")
    var fields_to_lock: [String] = []
    fields_to_lock.append("asdf_test_field")
    certification_sig_field.setFieldPermissions(e_ptdigsig_permission_include, in_field_names: fields_to_lock)
    
    certification_sig_field.certify(onNextSave: in_private_key_file_path, in_password: in_keyfile_password)

    // (OPTIONAL) Add more information to the signature dictionary.
    certification_sig_field.setLocation("Vancouver, BC")
    certification_sig_field.setReason("Document certification.")
    certification_sig_field.setContactInfo("www.pdftron.com")

    // Save the PDFDoc. Once the method below is called, PDFNet will also sign the document using the information provided.
    doc.save(toFile: in_outpath, flags: 0)

    print("================================================================================")
}

func 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?) {
    print("================================================================================")
    print("Signing PDF document")

    // Open an existing PDF
    let doc: PTPDFDoc = PTPDFDoc(filepath: in_docpath)

    // Retrieve the unsigned approval signature field.
    let found_approval_field: PTField = doc.getField(in_approval_field_name)
    let found_approval_signature_digsig_field: PTDigitalSignatureField = PTDigitalSignatureField(in_field: found_approval_field)

    // (OPTIONAL) Add an appearance to the signature field.
    let img: PTImage = PTImage.create(withFile: doc.getSDFDoc(), filename: in_appearance_img_path, encoder_hints: PTObj())
    let found_approval_signature_widget: PTSignatureWidget = PTSignatureWidget(d: found_approval_field.getSDFObj())
    found_approval_signature_widget.createSignatureAppearance(img)

    // Prepare the signature and signature handler for signing.
    found_approval_signature_digsig_field.sign(onNextSave: in_private_key_file_path, in_password: in_keyfile_password)

    // The actual approval signing will be done during the following incremental save operation.
    doc.save(toFile: in_outpath, flags: e_ptincremental.rawValue)

    print("================================================================================")
}

func ClearSignature(_ in_docpath: String?, _ in_digsig_field_name: String?, _ in_outpath: String?) {
    print("================================================================================")
    print("Clearing certification signature")
    
    let doc: PTPDFDoc = PTPDFDoc(filepath: in_docpath)
    
    let digsig: PTDigitalSignatureField = PTDigitalSignatureField(in_field: doc.getField(in_digsig_field_name))
    
    print("Clearing signature: \(in_digsig_field_name ?? "")")
    digsig.clearSignature()
    
    if !digsig.hasCryptographicSignature() {
        print("Cryptographic signature cleared properly.")
    }
    
    // Save incrementally so as to not invalidate other signatures' hashes from previous saves.
    doc.save(toFile: in_outpath, flags: e_ptincremental.rawValue)

    print("================================================================================")
}

func PrintSignaturesInfo(_ in_docpath: String?) {
    print("================================================================================")
    print("Reading and printing digital signature information")

    let doc: PTPDFDoc = PTPDFDoc(filepath: in_docpath)
    if !doc.hasSignatures() {
        print("Doc has no signatures.")
        print("================================================================================")
        return
    } else {
        print("Doc has signatures.")
    }

    let fitr: PTFieldIterator = doc.getFieldIterator()

    while fitr.hasNext() {
        defer {
            fitr.next()
        }
        
        print("==========")
        if fitr.current().isLockedByDigitalSignature() {
            print("Field locked by a digital signature")
        } else {
            print("Field not locked by a digital signature")
        }

        print("Field name: \(fitr.current().getName() ?? "")")
        print("==========")
    }
    
    print("====================")
    print("Now iterating over digital signatures only.")
    print("====================")

    let digsig_fitr: PTDigitalSignatureFieldIterator = doc.getDigitalSignatureFieldIterator()

    while digsig_fitr.hasNext() {
        defer {
            digsig_fitr.next()
        }
        
        print("==========")
        print("Field name of digital signature: \(PTField(field_dict: digsig_fitr.current().getSDFObj()).getName() ?? "")")

        let digsigfield: PTDigitalSignatureField = digsig_fitr.current()
        if !digsigfield.hasCryptographicSignature() {
            print("""
            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.
            """)
            print("==========")
            continue
        }

        let cert_count = digsigfield.getCertCount()
        print("Cert count: \(cert_count)")
        for i in 0..<cert_count {
            let cert: Data = digsigfield.getCert(i)
            print(String(format: "Cert #%d size: %lu", i, cert.count))
        }

        let subfilter = digsigfield.getSubFilter()

        print("Subfilter type: \(subfilter)")

        if subfilter != e_ptETSI_RFC3161 {
            print("Signature's signer: \(digsigfield.getSignatureName() ?? "")")

            let signing_time: PTDate = digsigfield.getSigningTime()
            if signing_time.isValid() {
                print("Signing day: \((signing_time as TRN_date).getDay())")
            }

            print("Location: \(digsigfield.getLocation() ?? "")")
            print("Reason: \(digsigfield.getReason() ?? "")")
            print("Contact info: \(digsigfield.getContactInfo() ?? "")")
        } else {
            print("SubFilter == e_ETSI_RFC3161 (DocTimeStamp; no signing info)")
        }

        print((digsigfield.hasVisibleAppearance()) ? "Visible" : "Not visible")
        
        let digsig_doc_perms = digsigfield.getDocumentPermissions()
        let locked_fields: [String]  = digsigfield.getLockedFields()
        for field_name in locked_fields {
            print("This digital signature locks a field named: \(field_name)")
        }
        
        switch digsig_doc_perms {
        case e_ptno_changes_allowed:
            print("No changes to the document can be made without invalidating this digital signature.")
        case e_ptformfilling_signing_allowed:
            print("Page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.")
        case e_ptannotating_formfilling_signing_allowed:
            print("Annotating, page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.")
        case e_ptunrestricted:
            print("Document not restricted by this digital signature.")
        default:
            //NSException.raise("Unrecognized digital signature document permission level.", format: "Unrecognized digital signature document permission level.")
            print()
        }
        print("==========")
    }

    print("================================================================================")
}

func runDigitalSignaturesTest() -> Int {
    return autoreleasepool {
        
        var result = true
        
        let input_path: String = (Bundle.main.resourcePath)! + ("/")
        let output_path: String = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]).appendingPathComponent("").path
        
        //////////////////// TEST 0:
        /* Create an approval signature field that we can sign after certifying.
         (Must be done before calling CertifyOnNextSave/SignOnNextSave/WithCustomHandler.) */
        do {
            try PTPDFNet.catchException {
                let doc: PTPDFDoc = PTPDFDoc(filepath: "\(input_path)\("tiger.pdf")")
                let approval_signature_field: PTDigitalSignatureField = doc.createDigitalSignatureField("PDFTronApprovalSig")
                let widgetAnnotApproval: PTSignatureWidget = PTSignatureWidget.create(withDigitalSignatureField: doc, pos: PTPDFRect(x1: 300, y1: 300, x2: 500, y2: 200), field: approval_signature_field)
                let page1: PTPage = doc.getPage(1)
                page1.annotPushBack(widgetAnnotApproval)
                doc.save(toFile: "\(output_path)\("tiger_withApprovalField_output.pdf")", flags: e_ptremove_unused.rawValue)
            }
        } catch {
            print("Exception: \(error)\n")
            result = false
        }
        
        do {
            try PTPDFNet.catchException {
                CertifyPDF("\(input_path)\("tiger_withApprovalField.pdf")",
                    "PDFTronCertificationSig",
                    "\(input_path)\("pdftron.pfx")",
                    "password",
                    "\(input_path)\("pdftron.bmp")",
                    "\(output_path)\("tiger_withApprovalField_certified_output.pdf")")
                PrintSignaturesInfo("\(output_path)\("tiger_withApprovalField_certified_output.pdf")")
            }
        } catch {
            print("Exception: \(error)\n")
            result = false
        }
        
        //////////////////// TEST 2: sign a PDF with a certification and an unsigned signature field in it.
        do {
            try PTPDFNet.catchException {
                SignPDF("\(input_path)\("tiger_withApprovalField_certified.pdf")",
                    "PDFTronApprovalSig",
                    "\(input_path)\("pdftron.pfx")",
                    "password",
                    "\(input_path)\("signature.jpg")",
                    "\(output_path)\("tiger_withApprovalField_certified_approved_output.pdf")")
                PrintSignaturesInfo("\(output_path)\("tiger_withApprovalField_certified_approved_output.pdf")")
            }
        } catch {
            print("Exception: \(error)\n")
            result = false
        }
        
        //////////////////// TEST 3: Clear a certification from a document that is certified and has an approval signature.
        do {
            try PTPDFNet.catchException {
                ClearSignature("\(input_path)\("tiger_withApprovalField_certified_approved.pdf")",
                    "PDFTronCertificationSig",
                    "\(output_path)\("tiger_withApprovalField_certified_approved_certcleared_output.pdf")")
                PrintSignaturesInfo("\(output_path)\("tiger_withApprovalField_certified_approved_certcleared_output.pdf")")
            }
        } catch {
            print("Exception: \(error)\n")
            result = false
        }
        
        //////////////////// TEST 4: Verify a document's digital signatures.
        do {
            try PTPDFNet.catchException {
                // EXPERIMENTAL. Digital signature verification is undergoing active development, but currently does not support a number of features. If we are missing a feature that is important to you, or if you have files that do not act as expected, please contact us using one of the following forms: https://apryse.com/form/trial-support/
                
                if !VerifyAllAndPrint("\(input_path)\("tiger_withApprovalField_certified_approved.pdf")", "\(input_path)\("pdftron.cer")") {
                    result = false
                }
            }
        } catch {
            print("Exception: \(error)\n")
            result = false
        }
        
        //////////////////// End of tests. ////////////////////

        if result {
            print("Tests successful.")
            print("==========")
        } else {
            print("Tests FAILED!!!")
            print("==========")
        }
        
        return result ? 1 : 0
    }
}