Digitally Sign PDF Files - C++ Sample Code

Sample code to use Apryse SDK's high-level digital signature API for digitally signing and/or certifying PDF files; provided in Python, C++, C#, Java, Node.js (JavaScript), PHP, Ruby and VB.. Learn more about our Server SDK and PDF Digital Signature Library.

1//---------------------------------------------------------------------------------------
2// Copyright (c) 2001-2024 by Apryse Software Inc. All Rights Reserved.
3// Consult legal.txt regarding legal and license information.
4//---------------------------------------------------------------------------------------
5
6//----------------------------------------------------------------------------------------------------------------------
7// This sample demonstrates the basic usage of the high-level digital signatures API in PDFNet.
8//
9// The following steps reflect typical intended usage of the digital signatures API:
10//
11// 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)).
12//
13// 1. EITHER:
14// (a) Call doc.CreateDigitalSignatureField, optionally providing a name. You receive a DigitalSignatureField.
15// -OR-
16// (b) If you didn't just create the digital signature field that you want to sign/certify, find the existing one within the
17// document by using PDFDoc.DigitalSignatureFieldIterator or by using PDFDoc.GetField to get it by its fully qualified name.
18//
19// 2. Create a signature widget annotation, and pass the DigitalSignatureField that you just created or found.
20// If you want it to be visible, provide a Rect argument with a non-zero width or height, and don't set the
21// NoView and Hidden flags. [Optionally, add an appearance to the annotation when you wish to sign/certify.]
22//
23// [3. (OPTIONAL) Add digital signature restrictions to the document using the field modification permissions (SetFieldPermissions)
24// or document modification permissions functions (SetDocumentPermissions) of DigitalSignatureField. These features disallow
25// certain types of changes to be made to the document without invalidating the cryptographic digital signature once it
26// is signed.]
27//
28// 4. Call either CertifyOnNextSave or SignOnNextSave. There are three overloads for each one (six total):
29// a. Taking a PKCS #12 keyfile path and its password
30// b. Taking a buffer containing a PKCS #12 private keyfile and its password
31// c. Taking a unique identifier of a signature handler registered with the PDFDoc. This overload is to be used
32// in the following fashion:
33// i) Extend and implement a new SignatureHandler. The SignatureHandler will be used to add or
34// validate/check a digital signature.
35// ii) Create an instance of the implemented SignatureHandler and register it with PDFDoc with
36// pdfdoc.AddSignatureHandler(). The method returns a SignatureHandlerId.
37// iii) Call SignOnNextSaveWithCustomHandler/CertifyOnNextSaveWithCustomHandler with the SignatureHandlerId.
38// NOTE: It is only possible to sign/certify one signature per call to the Save function.
39//
40// 5. Call pdfdoc.Save(). This will also create the digital signature dictionary and write a cryptographic signature to it.
41// IMPORTANT: If there are already signed/certified digital signature(s) in the document, you must save incrementally
42// so as to not invalidate the other signature(s).
43//
44// Additional processing can be done before document is signed. For example, UseSignatureHandler() returns an instance
45// of SDF dictionary which represents the signature dictionary (or the /V entry of the form field). This can be used to
46// add additional information to the signature dictionary (e.g. Name, Reason, Location, etc.).
47//
48// Although the steps above describes extending the SignatureHandler class, this sample demonstrates the use of
49// StdSignatureHandler (a built-in SignatureHandler in PDFNet) to sign a PDF file.
50//----------------------------------------------------------------------------------------------------------------------
51
52// To build and run this sample with OpenSSL, please specify OpenSSL include & lib paths to project settings.
53//
54// In MSVC, this can be done by opening the DigitalSignatureTest project's properties. Go to Configuration Properties ->
55// C/C++ -> General -> Additional Include Directories. Add the path to the OpenSSL headers here. Next, go to
56// Configuration Properties -> Linker -> General -> Additional Library Directories. Add the path to the OpenSSL libraries
57// here. Finally, under Configuration Properties -> Linker -> Input -> Additional Dependencies, add libeay32.lib,
58// crypt32.lib, and advapi32.lib in the list.
59//
60// For GCC, modify the Makefile, add -lcrypto to the $(LIBS) variable. If OpenSSL is installed elsewhere, it may be
61// necessary to add the path to the headers in the $(INCLUDE) variable as well as the location of either libcrypto.a or
62// libcrypto.so/libcrypto.dylib.
63//
64
65#define USE_STD_SIGNATURE_HANDLER 1 // Comment out this line if you intend to use OpenSSLSignatureHandler rather than StdSignatureHandler.
66
67// standard library includes
68#include <cstdio>
69#include <iostream>
70#include <vector>
71
72// PDFNetC includes
73#include <Common/Exception.h>
74#include <Common/UString.h>
75#include <PDF/Page.h>
76#include <PDF/Annot.h>
77#include <PDF/Annots/TextWidget.h>
78#include <PDF/Date.h>
79#include <PDF/Element.h>
80#include <PDF/ElementBuilder.h>
81#include <PDF/ElementWriter.h>
82#include <PDF/Field.h>
83#include <PDF/Image.h>
84#include <PDF/PDFDoc.h>
85#include <PDF/PDFNet.h>
86#include <SDF/SignatureHandler.h>
87#include <PDF/Annots/SignatureWidget.h>
88#include <PDF/VerificationResult.h>
89#include <PDF/TrustVerificationResult.h>
90#include <PDF/DisallowedChange.h>
91#include <Filters/MappedFile.h>
92#include <Crypto/X501AttributeTypeAndValue.h>
93#include "../../LicenseKey/CPP/LicenseKey.h"
94
95#if (!USE_STD_SIGNATURE_HANDLER)
96// OpenSSL includes
97#include <openssl/err.h>
98#include <openssl/evp.h>
99#include <openssl/pkcs12.h>
100#include <openssl/pkcs7.h>
101#include <openssl/rsa.h>
102#include <openssl/sha.h>
103#endif // (!USE_STD_SIGNATURE_HANDLER)
104
105using namespace std;
106using namespace pdftron;
107using namespace pdftron::SDF;
108using namespace pdftron::PDF::Annots;
109using namespace pdftron::PDF;
110
111//////////////////// Here follows an example of how to implement a custom signature handler. //////////
112#if (!USE_STD_SIGNATURE_HANDLER)
113//
114// Extend SignatureHandler by using OpenSSL signing utilities.
115//
116class OpenSSLSignatureHandler : public SignatureHandler
117{
118public:
119 OpenSSLSignatureHandler(const char* in_pfxfile, const char* in_password) : m_pfxfile(in_pfxfile), m_password(in_password)
120 {
121 // Please note: this code would require changes to support non-US-ASCII paths on platforms where UTF-8 is not usable within fopen (e.g. Windows).
122 FILE* fp = fopen(in_pfxfile, "rb");
123 if (fp == NULL)
124 throw (Common::Exception("Cannot open private key.", __LINE__, __FILE__, "PKCS7Signature::PKCS7Signature", "Cannot open private key."));
125
126 PKCS12* p12 = d2i_PKCS12_fp(fp, NULL);
127 fclose(fp);
128
129 if (p12 == NULL)
130 throw (Common::Exception("Cannot parse private key.", __LINE__, __FILE__, "PKCS7Signature::PKCS7Signature", "Cannot parse private key."));
131
132 mp_pkey = NULL;
133 mp_x509 = NULL;
134 mp_ca = NULL;
135 int parseResult = PKCS12_parse(p12, in_password, &mp_pkey, &mp_x509, &mp_ca);
136 PKCS12_free(p12);
137
138 if (parseResult == 0)
139 throw (Common::Exception("Cannot parse private key.", __LINE__, __FILE__, "PKCS7Signature::PKCS7Signature", "Cannot parse private key."));
140
141 Reset();
142 }
143
144 virtual UString GetName() const
145 {
146 return "Adobe.PPKLite";
147 }
148
149 virtual void AppendData(const std::vector<pdftron::UInt8>& in_data)
150 {
151 SHA256_Update(&m_sha_ctx, (const void*) &(in_data[0]), in_data.size());
152 return;
153 }
154
155 virtual bool Reset()
156 {
157 m_digest.resize(0);
158 m_digest.clear();
159 SHA256_Init(&m_sha_ctx);
160 return (true);
161 }
162
163 virtual std::vector<pdftron::UInt8> CreateSignature()
164 {
165 if (m_digest.size() == 0) {
166 m_digest.resize(SHA256_DIGEST_LENGTH);
167 SHA256_Final(&(m_digest[0]), &m_sha_ctx);
168 }
169
170 PKCS7* p7 = PKCS7_new();
171 PKCS7_set_type(p7, NID_pkcs7_signed);
172
173 PKCS7_SIGNER_INFO* p7Si = PKCS7_add_signature(p7, mp_x509, mp_pkey, EVP_sha256());
174 PKCS7_add_attrib_content_type(p7Si, OBJ_nid2obj(NID_pkcs7_data));
175 PKCS7_add0_attrib_signing_time(p7Si, NULL);
176 PKCS7_add1_attrib_digest(p7Si, &(m_digest[0]), (int)m_digest.size());
177 PKCS7_add_certificate(p7, mp_x509);
178
179 for (int c = 0; c < sk_X509_num(mp_ca); c++) {
180 X509* cert = sk_X509_value(mp_ca, c);
181 PKCS7_add_certificate(p7, cert);
182 }
183 PKCS7_set_detached(p7, 1);
184 PKCS7_content_new(p7, NID_pkcs7_data);
185
186 PKCS7_SIGNER_INFO_sign(p7Si);
187
188 int p7Len = i2d_PKCS7(p7, NULL);
189 std::vector<unsigned char> result(p7Len);
190 UInt8* pP7Buf = &(result[0]);
191 i2d_PKCS7(p7, &pP7Buf);
192
193 PKCS7_free(p7);
194
195 return (result);
196 }
197
198 virtual OpenSSLSignatureHandler* Clone() const
199 {
200 return (new OpenSSLSignatureHandler(m_pfxfile.c_str(), m_password.c_str()));
201 }
202
203 virtual ~OpenSSLSignatureHandler()
204 {
205 sk_X509_free(mp_ca);
206 X509_free(mp_x509);
207 EVP_PKEY_free(mp_pkey);
208 }
209
210private:
211 std::vector<UInt8> m_digest;
212 std::string m_pfxfile;
213 std::string m_password;
214
215 SHA256_CTX m_sha_ctx;
216 EVP_PKEY* mp_pkey; // private key
217 X509* mp_x509; // signing certificate
218 STACK_OF(X509)* mp_ca; // certificate chain up to the CA
219}; // class OpenSSLSignatureHandler
220#endif // (!USE_STD_SIGNATURE_HANDLER)
221////////// End of the OpenSSLSignatureHandler custom handler code. ////////////////////
222
223
224string input_path = "../../TestFiles/";
225string output_path = "../../TestFiles/Output/";
226
227bool VerifySimple(const UString& in_docpath, const UString& in_public_key_file_path)
228{
229 PDFDoc doc(in_docpath);
230 puts("==========");
231 VerificationOptions opts(VerificationOptions::e_compatibility_and_archiving);
232
233 // Add trust root to store of trusted certificates contained in VerificationOptions.
234 opts.AddTrustedCertificate(in_public_key_file_path, VerificationOptions::e_default_trust | VerificationOptions::e_certification_trust);
235
236 enum PDFDoc::SignaturesVerificationStatus result = doc.VerifySignedDigitalSignatures(opts);
237 switch (result)
238 {
239 case PDFDoc::e_unsigned:
240 puts("Document has no signed signature fields.");
241 return false;
242 /* e_failure == bad doc status, digest status, or permissions status
243 (i.e. does not include trust issues, because those are flaky due to being network/config-related) */
244 case PDFDoc::e_failure:
245 puts("Hard failure in verification on at least one signature.");
246 return false;
247 case PDFDoc::e_untrusted:
248 puts("Could not verify trust for at least one signature.");
249 return false;
250 case PDFDoc::e_unsupported:
251 /* If necessary, call GetUnsupportedFeatures on VerificationResult to check which
252 unsupported features were encountered (requires verification using 'detailed' APIs) */
253 puts("At least one signature contains unsupported features.");
254 return false;
255 // unsigned sigs skipped; parts of document may be unsigned (check GetByteRanges on signed sigs to find out)
256 case PDFDoc::e_verified:
257 puts("All signed signatures in document verified.");
258 return true;
259 default:
260 BASE_ASSERT(false, "unrecognized document verification status");
261 }
262}
263
264bool VerifyAllAndPrint(const UString& in_docpath, const UString& in_public_key_file_path)
265{
266 PDFDoc doc(in_docpath);
267 cout << "==========" << endl;
268 PDF::VerificationOptions opts(PDF::VerificationOptions::e_compatibility_and_archiving);
269
270 /* Add trust root to store of trusted certificates contained in VerificationOptions.
271 Use trust level corresponding to an identity trusted even for certification signatures. */
272 opts.AddTrustedCertificate(in_public_key_file_path, VerificationOptions::e_default_trust | VerificationOptions::e_certification_trust);
273
274 // Iterate over the signatures and verify all of them.
275 DigitalSignatureFieldIterator digsig_fitr = doc.GetDigitalSignatureFieldIterator();
276 bool verification_status = true;
277 for (; digsig_fitr.HasNext(); digsig_fitr.Next())
278 {
279 DigitalSignatureField curr = digsig_fitr.Current();
280 VerificationResult result = curr.Verify(opts);
281 if (result.GetVerificationStatus())
282 {
283 cout << "Signature verified, ";
284 }
285 else
286 {
287 cout << "Signature verification failed, ";
288 verification_status = false;
289 }
290 cout << "objnum: " << curr.GetSDFObj().GetObjNum() << endl;
291
292 switch (result.GetDigestAlgorithm())
293 {
294 case Crypto::DigestAlgorithm::e_SHA1:
295 cout << "Digest algorithm: SHA-1" << endl;
296 break;
297 case Crypto::DigestAlgorithm::e_SHA256:
298 cout << "Digest algorithm: SHA-256" << endl;
299 break;
300 case Crypto::DigestAlgorithm::e_SHA384:
301 cout << "Digest algorithm: SHA-384" << endl;
302 break;
303 case Crypto::DigestAlgorithm::e_SHA512:
304 cout << "Digest algorithm: SHA-512" << endl;
305 break;
306 case Crypto::DigestAlgorithm::e_RIPEMD160:
307 cout << "Digest algorithm: RIPEMD-160" << endl;
308 break;
309 case Crypto::DigestAlgorithm::e_unknown_digest_algorithm:
310 cout << "Digest algorithm: unknown" << endl;
311 break;
312 default:
313 BASE_ASSERT(false, "unrecognized digest algorithm");
314 }
315 printf("Detailed verification result: \n\t%s\n\t%s\n\t%s\n\t%s\n",
316 result.GetDocumentStatusAsString().ConvertToUtf8().c_str(),
317 result.GetDigestStatusAsString().ConvertToUtf8().c_str(),
318 result.GetTrustStatusAsString().ConvertToUtf8().c_str(),
319 result.GetPermissionsStatusAsString().ConvertToUtf8().c_str());
320
321
322 std::vector<DisallowedChange> changes = result.GetDisallowedChanges();
323 for (std::vector<DisallowedChange>::iterator it2 = changes.begin(); it2 != changes.end(); ++it2)
324 {
325 cout << "\tDisallowed change: " << it2->GetTypeAsString().ConvertToUtf8() << ", objnum: " << it2->GetObjNum() << endl;
326 }
327
328 // Get and print all the detailed trust-related results, if they are available.
329 if (result.HasTrustVerificationResult())
330 {
331 const TrustVerificationResult trust_verification_result = result.GetTrustVerificationResult();
332 trust_verification_result.WasSuccessful()? cout << "Trust verified." << endl : cout << "Trust not verifiable." << endl;
333 cout << trust_verification_result.GetResultString().ConvertToUtf8() << endl;
334
335 Int64 tmp_time_t = trust_verification_result.GetTimeOfTrustVerification();
336 switch (trust_verification_result.GetTimeOfTrustVerificationEnum())
337 {
338 case VerificationOptions::e_current:
339 cout << "Trust verification attempted with respect to current time (as epoch time): " << tmp_time_t << endl;
340 break;
341 case VerificationOptions::e_signing:
342 cout << "Trust verification attempted with respect to signing time (as epoch time): " << tmp_time_t << endl;
343 break;
344 case VerificationOptions::e_timestamp:
345 cout << "Trust verification attempted with respect to secure embedded timestamp (as epoch time): " << tmp_time_t << endl;
346 break;
347 default:
348 BASE_ASSERT(false, "unrecognized time enum value");
349 }
350
351 if (trust_verification_result.GetCertPath().empty())
352 {
353 cout << "Could not print certificate path.\n";
354 }
355 else
356 {
357 cout << "Certificate path:\n";
358 std::vector<Crypto::X509Certificate> cert_path(trust_verification_result.GetCertPath());
359 for (std::vector<Crypto::X509Certificate>::iterator cert_path_begin = cert_path.begin();
360 cert_path_begin != cert_path.end(); ++cert_path_begin)
361 {
362 cout << "\tCertificate:\n";
363 Crypto::X509Certificate& full_cert(*cert_path_begin);
364 cout << "\t\tIssuer names:\n";
365 std::vector<Crypto::X501AttributeTypeAndValue> issuer_dn (full_cert.GetIssuerField().GetAllAttributesAndValues());
366 for (size_t i = 0; i < issuer_dn.size(); i++)
367 {
368 cout << "\t\t\t" << issuer_dn[i].GetStringValue().ConvertToUtf8() << "\n";
369 }
370 cout << "\t\tSubject names:\n";
371 std::vector<Crypto::X501AttributeTypeAndValue > subject_dn(full_cert.GetSubjectField().GetAllAttributesAndValues());
372 for (size_t i = 0; i < subject_dn.size(); i++)
373 {
374 cout << "\t\t\t" << subject_dn[i].GetStringValue().ConvertToUtf8() << "\n";
375 }
376 cout << "\t\tExtensions:\n";
377 for (size_t i = 0; i < full_cert.GetExtensions().size(); i++)
378 {
379 cout << "\t\t\t" << full_cert.GetExtensions()[i].ToString() << "\n";
380 }
381 }
382 }
383 }
384 else
385 {
386 cout << "No detailed trust verification result available." << endl;
387 }
388
389 const std::vector<UString> unsupported_features(result.GetUnsupportedFeatures());
390 if (unsupported_features.size() > 0)
391 {
392 cout << "Unsupported features:" << "\n";
393 for (size_t i = 0; i < unsupported_features.size(); i++)
394 {
395 cout << "\t" << unsupported_features[i].ConvertToUtf8() << "\n";
396 }
397 }
398 cout << "==========" << endl;
399 }
400
401 return verification_status;
402}
403
404void CertifyPDF(const UString& in_docpath,
405 const UString& in_cert_field_name,
406 const UString& in_private_key_file_path,
407 const UString& in_keyfile_password,
408 const UString& in_appearance_image_path,
409 const UString& in_outpath)
410{
411 cout << "================================================================================" << endl;
412 cout << "Certifying PDF document" << endl;
413
414 // Open an existing PDF
415 PDFDoc doc(in_docpath);
416
417 cout << "PDFDoc has " << (doc.HasSignatures() ? "signatures" : "no signatures") << endl;
418
419 Page page1 = doc.GetPage(1);
420
421 // Create a text field that we can lock using the field permissions feature.
422 Annots::TextWidget annot1 = Annots::TextWidget::Create(doc, Rect(143, 440, 350, 460), "asdf_test_field");
423 page1.AnnotPushBack(annot1);
424
425 /* Create a new signature form field in the PDFDoc. The name argument is optional;
426 leaving it empty causes it to be auto-generated. However, you may need the name for later.
427 Acrobat doesn't show digsigfield in side panel if it's without a widget. Using a
428 Rect with 0 width and 0 height, or setting the NoPrint/Invisible flags makes it invisible. */
429 PDF::DigitalSignatureField certification_sig_field = doc.CreateDigitalSignatureField(in_cert_field_name);
430 Annots::SignatureWidget widgetAnnot = Annots::SignatureWidget::Create(doc, Rect(143, 287, 219, 306), certification_sig_field);
431 page1.AnnotPushBack(widgetAnnot);
432
433 // (OPTIONAL) Add an appearance to the signature field.
434 PDF::Image img = PDF::Image::Create(doc, in_appearance_image_path);
435 widgetAnnot.CreateSignatureAppearance(img);
436
437 // Prepare the document locking permission level. It will be applied upon document certification.
438 cout << "Adding document permissions." << endl;
439 certification_sig_field.SetDocumentPermissions(DigitalSignatureField::e_annotating_formfilling_signing_allowed);
440
441 // Prepare to lock the text field that we created earlier.
442 cout << "Adding field permissions." << endl;
443 vector<UString> fields_to_lock;
444 fields_to_lock.push_back("asdf_test_field");
445 certification_sig_field.SetFieldPermissions(DigitalSignatureField::e_include, fields_to_lock);
446
447#ifdef USE_STD_SIGNATURE_HANDLER
448 certification_sig_field.CertifyOnNextSave(in_private_key_file_path, in_keyfile_password);
449#else
450 OpenSSLSignatureHandler sigHandler(in_private_key_file_path.ConvertToUtf8().c_str(), in_keyfile_password.ConvertToUtf8().c_str());
451 SignatureHandlerId sigHandlerId = doc.AddSignatureHandler(sigHandler);
452 certification_sig_field.CertifyOnNextSaveWithCustomHandler(sigHandlerId);
453 /* Add to the digital signature dictionary a SubFilter name that uniquely identifies the signature format
454 for verification tools. As an example, the custom handler defined in this file uses the CMS/PKCS #7 detached format,
455 so we embed one of the standard predefined SubFilter values: "adbe.pkcs7.detached". It is not necessary to do this
456 when using the StdSignatureHandler. */
457 Obj f_obj = certification_sig_field.GetSDFObj();
458 f_obj.FindObj("V").PutName("SubFilter", "adbe.pkcs7.detached");
459#endif
460
461 // (OPTIONAL) Add more information to the signature dictionary.
462 certification_sig_field.SetLocation("Vancouver, BC");
463 certification_sig_field.SetReason("Document certification.");
464 certification_sig_field.SetContactInfo("www.pdftron.com");
465
466 // Save the PDFDoc. Once the method below is called, PDFNet will also sign the document using the information provided.
467 doc.Save(in_outpath, 0, NULL);
468
469 cout << "================================================================================" << endl;
470}
471
472void SignPDF(const UString& in_docpath,
473 const UString& in_approval_field_name,
474 const UString& in_private_key_file_path,
475 const UString& in_keyfile_password,
476 const UString& in_appearance_img_path,
477 const UString& in_outpath)
478{
479 cout << "================================================================================" << endl;
480 cout << "Signing PDF document" << endl;
481
482 // Open an existing PDF
483 PDFDoc doc(in_docpath);
484
485 // Retrieve the unsigned approval signature field.
486 Field found_approval_field(doc.GetField(in_approval_field_name));
487 PDF::DigitalSignatureField found_approval_signature_digsig_field(found_approval_field);
488
489 // (OPTIONAL) Add an appearance to the signature field.
490 PDF::Image img = PDF::Image::Create(doc, in_appearance_img_path);
491 Annots::SignatureWidget found_approval_signature_widget(found_approval_field.GetSDFObj());
492 found_approval_signature_widget.CreateSignatureAppearance(img);
493
494 // Prepare the signature and signature handler for signing.
495#ifdef USE_STD_SIGNATURE_HANDLER
496 found_approval_signature_digsig_field.SignOnNextSave(in_private_key_file_path, in_keyfile_password);
497#else
498 OpenSSLSignatureHandler sigHandler(in_private_key_file_path.ConvertToUtf8().c_str(), in_keyfile_password.ConvertToUtf8().c_str());
499 SignatureHandlerId sigHandlerId = doc.AddSignatureHandler(sigHandler);
500 found_approval_signature_digsig_field.SignOnNextSaveWithCustomHandler(sigHandlerId);
501 /* Add to the digital signature dictionary a SubFilter name that uniquely identifies the signature format
502 for verification tools. As an example, the custom handler defined in this file uses the CMS/PKCS #7 detached format,
503 so we embed one of the standard predefined SubFilter values: "adbe.pkcs7.detached". It is not necessary to do this
504 when using the StdSignatureHandler. */
505 Obj f_obj = found_approval_signature_digsig_field.GetSDFObj();
506 f_obj.FindObj("V").PutName("SubFilter", "adbe.pkcs7.detached");
507#endif
508
509 // The actual approval signing will be done during the following incremental save operation.
510 doc.Save(in_outpath, SDFDoc::e_incremental, NULL);
511
512 cout << "================================================================================" << endl;
513}
514
515void ClearSignature(const UString& in_docpath,
516 const UString& in_digsig_field_name,
517 const UString& in_outpath)
518{
519 cout << "================================================================================" << endl;
520 cout << "Clearing certification signature" << endl;
521
522 PDFDoc doc(in_docpath);
523
524 DigitalSignatureField digsig(doc.GetField(in_digsig_field_name));
525
526 cout << "Clearing signature: " << in_digsig_field_name << endl;
527 digsig.ClearSignature();
528
529 if (!digsig.HasCryptographicSignature())
530 {
531 cout << "Cryptographic signature cleared properly." << endl;
532 }
533
534 // Save incrementally so as to not invalidate other signatures from previous saves.
535 doc.Save(in_outpath, SDFDoc::e_incremental, NULL);
536
537 cout << "================================================================================" << endl;
538}
539
540void PrintSignaturesInfo(const UString& in_docpath)
541{
542 cout << "================================================================================" << endl;
543 cout << "Reading and printing digital signature information" << endl;
544
545 PDFDoc doc(in_docpath);
546 if (!doc.HasSignatures())
547 {
548 cout << "Doc has no signatures." << endl;
549 cout << "================================================================================" << endl;
550 return;
551 }
552 else
553 {
554 cout << "Doc has signatures." << endl;
555 }
556
557
558 for (FieldIterator fitr = doc.GetFieldIterator(); fitr.HasNext(); fitr.Next())
559 {
560 fitr.Current().IsLockedByDigitalSignature() ? cout << "==========" << endl << "Field locked by a digital signature" << endl :
561 cout << "==========" << endl << "Field not locked by a digital signature" << endl;
562
563 cout << "Field name: " << fitr.Current().GetName() << endl;
564 cout << "==========" << endl;
565 }
566
567 cout << "====================" << endl << "Now iterating over digital signatures only." << endl << "====================" << endl;
568
569 DigitalSignatureFieldIterator digsig_fitr = doc.GetDigitalSignatureFieldIterator();
570 for (; digsig_fitr.HasNext(); digsig_fitr.Next())
571 {
572 cout << "==========" << endl;
573 cout << "Field name of digital signature: " << Field(digsig_fitr.Current().GetSDFObj()).GetName() << endl;
574
575 DigitalSignatureField digsigfield(digsig_fitr.Current());
576 if (!digsigfield.HasCryptographicSignature())
577 {
578 cout << "Either digital signature field lacks a digital signature dictionary, "
579 "or digital signature dictionary lacks a cryptographic Contents entry. "
580 "Digital signature field is not presently considered signed." << endl
581 << "==========" << endl;
582 continue;
583 }
584
585 UInt32 cert_count = digsigfield.GetCertCount();
586 cout << "Cert count: " << cert_count << endl;
587 for (UInt32 i = 0; i < cert_count; ++i)
588 {
589 std::vector<unsigned char> cert = digsigfield.GetCert(i);
590 cout << "Cert #" << i << " size: " << cert.size() << endl;
591 }
592
593 DigitalSignatureField::SubFilterType subfilter = digsigfield.GetSubFilter();
594
595 cout << "Subfilter type: " << (int)subfilter << endl;
596
597 if (subfilter != DigitalSignatureField::e_ETSI_RFC3161)
598 {
599 cout << "Signature's signer: " << digsigfield.GetSignatureName() << endl;
600
601 Date signing_time(digsigfield.GetSigningTime());
602 if (signing_time.IsValid())
603 {
604 cout << "Signing time is valid." << endl;
605 }
606
607 cout << "Location: " << digsigfield.GetLocation() << endl;
608 cout << "Reason: " << digsigfield.GetReason() << endl;
609 cout << "Contact info: " << digsigfield.GetContactInfo() << endl;
610 }
611 else
612 {
613 cout << "SubFilter == e_ETSI_RFC3161 (DocTimeStamp; no signing info)" << endl;
614 }
615
616 cout << ((digsigfield.HasVisibleAppearance()) ? "Visible" : "Not visible") << endl;
617
618 DigitalSignatureField::DocumentPermissions digsig_doc_perms = digsigfield.GetDocumentPermissions();
619 vector<UString> locked_fields(digsigfield.GetLockedFields());
620 for (vector<UString>::iterator it = locked_fields.begin(); it != locked_fields.end(); ++it)
621 {
622 cout << "This digital signature locks a field named: " << it->ConvertToAscii() << endl;
623 }
624
625 switch (digsig_doc_perms)
626 {
627 case DigitalSignatureField::e_no_changes_allowed:
628 cout << "No changes to the document can be made without invalidating this digital signature." << endl;
629 break;
630 case DigitalSignatureField::e_formfilling_signing_allowed:
631 cout << "Page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature." << endl;
632 break;
633 case DigitalSignatureField::e_annotating_formfilling_signing_allowed:
634 cout << "Annotating, page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature." << endl;
635 break;
636 case DigitalSignatureField::e_unrestricted:
637 cout << "Document not restricted by this digital signature." << endl;
638 break;
639 default:
640 BASE_ASSERT(false, "Unrecognized digital signature document permission level.");
641 }
642 cout << "==========" << endl;
643 }
644
645 cout << "================================================================================" << endl;
646}
647
648void CustomSigningAPI(const UString& doc_path,
649 const UString& cert_field_name,
650 const UString& private_key_file_path,
651 const UString& keyfile_password,
652 const UString& public_key_file_path,
653 const UString& appearance_image_path,
654 const Crypto::DigestAlgorithm::Type digest_algorithm_type,
655 const bool PAdES_signing_mode,
656 const UString& output_path)
657{
658 cout << "================================================================================" << endl;
659 cout << "Custom signing PDF document" << endl;
660
661 PDFDoc doc(doc_path);
662
663 Page page1 = doc.GetPage(1);
664
665 DigitalSignatureField digsig_field = doc.CreateDigitalSignatureField(cert_field_name);
666 Annots::SignatureWidget widgetAnnot = Annots::SignatureWidget::Create(doc, Rect(143, 287, 219, 306), digsig_field);
667 page1.AnnotPushBack(widgetAnnot);
668
669 // (OPTIONAL) Add an appearance to the signature field.
670 PDF::Image img = PDF::Image::Create(doc, appearance_image_path);
671 widgetAnnot.CreateSignatureAppearance(img);
672
673 // Create a digital signature dictionary inside the digital signature field, in preparation for signing.
674 digsig_field.CreateSigDictForCustomSigning("Adobe.PPKLite",
675 PAdES_signing_mode ? DigitalSignatureField::e_ETSI_CAdES_detached : DigitalSignatureField::e_adbe_pkcs7_detached,
676 7500); // For security reasons, set the contents size to a value greater than but as close as possible to the size you expect your final signature to be, in bytes.
677 // ... or, if you want to apply a certification signature, use CreateSigDictForCustomCertification instead.
678
679 // (OPTIONAL) Set the signing time in the signature dictionary, if no secure embedded timestamping support is available from your signing provider.
680 Date current_date;
681 current_date.SetCurrentTime();
682 digsig_field.SetSigDictTimeOfSigning(current_date);
683
684 doc.Save(output_path, SDFDoc::e_incremental, NULL);
685
686 // Digest the relevant bytes of the document in accordance with ByteRanges surrounding the signature.
687 std::vector<UChar> pdf_digest = digsig_field.CalculateDigest(digest_algorithm_type);
688
689 Crypto::X509Certificate signer_cert(public_key_file_path);
690
691 // Optionally, you can add a custom signed attribute at this point, such as one of the PAdES ESS attributes.
692 // The function we provide takes care of generating the correct PAdES ESS attribute depending on your digest algorithm.
693 std::vector<UChar> pades_versioned_ess_signing_cert_attribute = DigitalSignatureField::GenerateESSSigningCertPAdESAttribute(signer_cert, digest_algorithm_type);
694
695 // Generate the signedAttrs component of CMS, passing any optional custom signedAttrs (e.g. PAdES ESS).
696 // The signedAttrs are certain attributes that become protected by their inclusion in the signature.
697 std::vector<UChar> signedAttrs = DigitalSignatureField::GenerateCMSSignedAttributes(pdf_digest.data(), pdf_digest.size(),
698 pades_versioned_ess_signing_cert_attribute.data(), pades_versioned_ess_signing_cert_attribute.size());
699
700 // Calculate the digest of the signedAttrs (i.e. not the PDF digest, this time).
701 std::vector<UChar> signedAttrs_digest = Crypto::DigestAlgorithm::CalculateDigest(digest_algorithm_type, signedAttrs.data(), signedAttrs.size());
702
703 //////////////////////////// custom digest signing starts ////////////////////////////
704 // At this point, you can sign the digest (for example, with HSM). We use our own SignDigest function instead here as an example,
705 // which you can also use for your purposes if necessary as an alternative to the handler/callback APIs (i.e. Certify/SignOnNextSave).
706 std::vector<UChar> signature_value = Crypto::DigestAlgorithm::SignDigest(
707 signedAttrs_digest.data(),
708 signedAttrs_digest.size(),
709 digest_algorithm_type,
710 private_key_file_path,
711 keyfile_password);
712 //////////////////////////// custom digest signing ends //////////////////////////////
713
714 // Then, load all your chain certificates into a container of X509Certificate.
715 std::vector<Crypto::X509Certificate> chain_certs;
716
717 // Then, create ObjectIdentifiers for the algorithms you have used.
718 // Here we use digest_algorithm_type (usually SHA256) for hashing, and RSAES-PKCS1-v1_5 (specified in the private key) for signing.
719 Crypto::ObjectIdentifier digest_algorithm_oid(digest_algorithm_type);
720 Crypto::ObjectIdentifier signature_algorithm_oid(Crypto::ObjectIdentifier::e_RSA_encryption_PKCS1);
721
722 // Then, put the CMS signature components together.
723 std::vector<UChar> cms_signature = DigitalSignatureField::GenerateCMSSignature(
724 signer_cert, chain_certs.data(), chain_certs.size(), digest_algorithm_oid, signature_algorithm_oid,
725 signature_value.data(), signature_value.size(), signedAttrs.data(), signedAttrs.size());
726
727 // Write the signature to the document.
728 doc.SaveCustomSignature(cms_signature.data(), cms_signature.size(), digsig_field, output_path);
729
730 cout << "================================================================================" << endl;
731}
732
733bool TimestampAndEnableLTV(const UString& doc_path,
734 const UString& tsa_url,
735 const UString& trusted_cert_path,
736 const UString& appearance_img_path,
737 const UString& output_path)
738{
739 PDFDoc doc(doc_path);
740 DigitalSignatureField doctimestamp_signature_field = doc.CreateDigitalSignatureField();
741 TimestampingConfiguration tst_config(tsa_url);
742 VerificationOptions opts(VerificationOptions::e_compatibility_and_archiving);
743 /* It is necessary to add to the VerificationOptions a trusted root certificate corresponding to
744 the chain used by the timestamp authority to sign the timestamp token, in order for the timestamp
745 response to be verifiable during DocTimeStamp signing. It is also necessary in the context of this
746 function to do this for the later LTV section, because one needs to be able to verify the DocTimeStamp
747 in order to enable LTV for it, and we re-use the VerificationOptions opts object in that part. */
748 opts.AddTrustedCertificate(trusted_cert_path);
749 /* By default, we only check online for revocation of certificates using the newer and lighter
750 OCSP protocol as opposed to CRL, due to lower resource usage and greater reliability. However,
751 it may be necessary to enable online CRL revocation checking in order to verify some timestamps
752 (i.e. those that do not have an OCSP responder URL for all non-trusted certificates). */
753 opts.EnableOnlineCRLRevocationChecking(true);
754
755 Annots::SignatureWidget widgetAnnot = Annots::SignatureWidget::Create(doc, Rect(0, 0, 0, 0), doctimestamp_signature_field);
756 doc.GetPage(1).AnnotPushBack(widgetAnnot);
757
758 puts("Testing timestamping configuration.");
759 const TimestampingResult config_result(tst_config.TestConfiguration(opts));
760 if (config_result.GetStatus())
761 {
762 puts("Success: timestamping configuration usable. Attempting to timestamp.");
763 }
764 else
765 {
766 // Print details of timestamping failure.
767 puts(config_result.GetString().ConvertToUtf8().c_str());
768 if (config_result.HasResponseVerificationResult())
769 {
770 EmbeddedTimestampVerificationResult tst_result(config_result.GetResponseVerificationResult());
771 printf("CMS digest status: %s\n", tst_result.GetCMSDigestStatusAsString().ConvertToUtf8().c_str());
772 printf("Message digest status: %s\n", tst_result.GetMessageImprintDigestStatusAsString().ConvertToUtf8().c_str());
773 printf("Trust status: %s\n", tst_result.GetTrustStatusAsString().ConvertToUtf8().c_str());
774 }
775 return false;
776 }
777
778 doctimestamp_signature_field.TimestampOnNextSave(tst_config, opts);
779
780 // Save/signing throws if timestamping fails.
781 doc.Save(output_path, SDFDoc::e_incremental, 0);
782
783 puts("Timestamping successful. Adding LTV information for DocTimeStamp signature.");
784
785 // Add LTV information for timestamp signature to document.
786 VerificationResult timestamp_verification_result = doctimestamp_signature_field.Verify(opts);
787 if (!doctimestamp_signature_field.EnableLTVOfflineVerification(timestamp_verification_result))
788 {
789 puts("Could not enable LTV for DocTimeStamp.");
790 return false;
791 }
792 doc.Save(output_path, SDFDoc::e_incremental, 0);
793 puts("Added LTV information for DocTimeStamp signature successfully.");
794
795 return true;
796}
797int main(void)
798{
799 // Initialize PDFNetC
800 PDFNet::Initialize(LicenseKey);
801
802#if (!USE_STD_SIGNATURE_HANDLER)
803 // Initialize OpenSSL library
804 CRYPTO_malloc_init();
805 ERR_load_crypto_strings();
806 OpenSSL_add_all_algorithms();
807#endif // (!USE_STD_SIGNATURE_HANDLER)
808
809 int ret = 0;
810
811 //////////////////// TEST 0:
812 /* Create an approval signature field that we can sign after certifying.
813 (Must be done before calling CertifyOnNextSave/SignOnNextSave/WithCustomHandler.) */
814 try
815 {
816 PDFDoc doc(input_path + "waiver.pdf");
817 DigitalSignatureField approval_signature_field = doc.CreateDigitalSignatureField("PDFTronApprovalSig");
818 Annots::SignatureWidget widgetAnnotApproval = Annots::SignatureWidget::Create(doc, Rect(300, 287, 376, 306), approval_signature_field);
819 Page page1 = doc.GetPage(1);
820 page1.AnnotPushBack(widgetAnnotApproval);
821 doc.Save(output_path + "waiver_withApprovalField_output.pdf", SDFDoc::e_remove_unused, 0);
822 }
823 catch (Common::Exception& e)
824 {
825 cerr << e << endl;
826 ret = 1;
827 }
828 catch (exception& e)
829 {
830 cerr << e.what() << endl;
831 ret = 1;
832 }
833 catch (...)
834 {
835 cerr << "Unknown exception." << endl;
836 ret = 1;
837 }
838
839 //////////////////// TEST 1: certify a PDF.
840 try
841 {
842 CertifyPDF(input_path + "waiver_withApprovalField.pdf",
843 "PDFTronCertificationSig",
844 input_path + "pdftron.pfx",
845 "password",
846 input_path + "pdftron.bmp",
847 output_path + "waiver_withApprovalField_certified_output.pdf");
848 PrintSignaturesInfo(output_path + "waiver_withApprovalField_certified_output.pdf");
849 }
850 catch (Common::Exception& e)
851 {
852 cerr << e << endl;
853 ret = 1;
854 }
855 catch (exception& e)
856 {
857 cerr << e.what() << endl;
858 ret = 1;
859 }
860 catch (...)
861 {
862 cerr << "Unknown exception." << endl;
863 ret = 1;
864 }
865
866 //////////////////// TEST 2: approval-sign an existing, unsigned signature field in a PDF that already has a certified signature field.
867 try
868 {
869 SignPDF(input_path + "waiver_withApprovalField_certified.pdf",
870 "PDFTronApprovalSig",
871 input_path + "pdftron.pfx",
872 "password",
873 input_path + "signature.jpg",
874 output_path + "waiver_withApprovalField_certified_approved_output.pdf");
875 PrintSignaturesInfo(output_path + "waiver_withApprovalField_certified_approved_output.pdf");
876 }
877 catch (Common::Exception& e)
878 {
879 cerr << e << endl;
880 ret = 1;
881 }
882 catch (exception& e)
883 {
884 cerr << e.what() << endl;
885 ret = 1;
886 }
887 catch (...)
888 {
889 cerr << "Unknown exception." << endl;
890 ret = 1;
891 }
892
893 //////////////////// TEST 3: Clear a certification from a document that is certified and has an approval signature.
894 try
895 {
896 ClearSignature(input_path + "waiver_withApprovalField_certified_approved.pdf",
897 "PDFTronCertificationSig",
898 output_path + "waiver_withApprovalField_certified_approved_certcleared_output.pdf");
899 PrintSignaturesInfo(output_path + "waiver_withApprovalField_certified_approved_certcleared_output.pdf");
900 }
901 catch (Common::Exception& e)
902 {
903 cerr << e << endl;
904 ret = 1;
905 }
906 catch (exception& e)
907 {
908 cerr << e.what() << endl;
909 ret = 1;
910 }
911 catch (...)
912 {
913 cerr << "Unknown exception." << endl;
914 ret = 1;
915 }
916
917 //////////////////// TEST 4: Verify a document's digital signatures.
918 try
919 {
920 if (!VerifyAllAndPrint(input_path + "waiver_withApprovalField_certified_approved.pdf", input_path + "pdftron.cer"))
921 {
922 ret = 1;
923 }
924 }
925 catch (Common::Exception& e)
926 {
927 cerr << e << endl;
928 ret = 1;
929 }
930 catch (exception& e)
931 {
932 cerr << e.what() << endl;
933 ret = 1;
934 }
935 catch (...)
936 {
937 cerr << "Unknown exception." << endl;
938 ret = 1;
939 }
940
941 //////////////////// TEST 5: Verify a document's digital signatures in a simple fashion using the document API.
942 try
943 {
944 if (!VerifySimple(input_path + "waiver_withApprovalField_certified_approved.pdf", input_path + "pdftron.cer"))
945 {
946 ret = 1;
947 }
948 }
949 catch (Common::Exception& e)
950 {
951 cerr << e << endl;
952 ret = 1;
953 }
954 catch (exception& e)
955 {
956 cerr << e.what() << "\n";
957 ret = 1;
958 }
959 catch (...)
960 {
961 cerr << "Unknown exception.\n";
962 ret = 1;
963 }
964
965 //////////////////// TEST 6: Custom signing API.
966 // The Apryse custom signing API is a set of APIs related to cryptographic digital signatures
967 // which allows users to customize the process of signing documents. Among other things, this
968 // includes the capability to allow for easy integration of PDF-specific signing-related operations
969 // with access to Hardware Security Module (HSM) tokens/devices, access to cloud keystores, access
970 // to system keystores, etc.
971 try
972 {
973 CustomSigningAPI(input_path + "waiver.pdf",
974 "PDFTronApprovalSig",
975 input_path + "pdftron.pfx",
976 "password",
977 input_path + "pdftron.cer",
978 input_path + "signature.jpg",
979 Crypto::DigestAlgorithm::e_SHA256,
980 true,
981 output_path + "waiver_custom_signed.pdf");
982 }
983 catch (Common::Exception& e)
984 {
985 cerr << e << endl;
986 ret = 1;
987 }
988 catch (exception& e)
989 {
990 cerr << e.what() << "\n";
991 ret = 1;
992 }
993 catch (...)
994 {
995 cerr << "Unknown exception.\n";
996 ret = 1;
997 }
998
999 //////////////////// TEST 7: Timestamp a document, then add Long Term Validation (LTV) information for the DocTimeStamp.
1000 //try
1001 //{
1002 // // Replace YOUR_URL_OF_TSA with the timestamp authority (TSA) URL to use during timestamping.
1003 // // For example, as of July 2024, http://timestamp.globalsign.com/tsa/r6advanced1 was usable.
1004 // // Note that this url may not work in the future. A reliable solution requires using your own TSA.
1005 // const UString tsa_url = "YOUR_URL_OF_TSA";
1006 // if (tsa_url == "YOUR_URL_OF_TSA")
1007 // {
1008 // throw exception("Error: The URL of your timestamp authority was not specified.");
1009 // }
1010 //
1011 // // Replace YOUR_CERTIFICATE with the trusted root certificate corresponding to the chain used by the timestamp authority.
1012 // // For example, as of July 2024, https://secure.globalsign.com/cacert/gstsacasha384g4.crt was usable.
1013 // // Note that this certificate may not work in the future. A reliable solution requires using your own TSA certificate.
1014 // const UString trusted_cert_path = "YOUR_CERTIFICATE";
1015 // if (trusted_cert_path == "YOUR_CERTIFICATE")
1016 // {
1017 // throw exception("Error: The path to your timestamp authority trusted root certificate was not specified.");
1018 // }
1019 //
1020 // if (!TimestampAndEnableLTV(input_path + "waiver.pdf",
1021 // tsa_url,
1022 // trusted_cert_path,
1023 // input_path + "signature.jpg",
1024 // output_path+ "waiver_DocTimeStamp_LTV.pdf"))
1025 // {
1026 // ret = 1;
1027 // }
1028 //}
1029 //catch (Common::Exception& e)
1030 //{
1031 // cerr << e << endl;
1032 // ret = 1;
1033 //}
1034 //catch (exception& e)
1035 //{
1036 // cerr << e.what() << "\n";
1037 // ret = 1;
1038 //}
1039 //catch (...)
1040 //{
1041 // cerr << "Unknown exception.\n";
1042 // ret = 1;
1043 //}
1044
1045 //////////////////// End of tests. ////////////////////
1046
1047 if (!ret)
1048 {
1049 cout << "Tests successful." << endl << "==========" << endl;
1050 }
1051 else
1052 {
1053 cout << "Tests FAILED!!!" << endl << "==========" << endl;
1054 }
1055
1056 PDFNet::Terminate();
1057
1058#if (!USE_STD_SIGNATURE_HANDLER)
1059 ERR_free_strings();
1060 EVP_cleanup();
1061#endif // (!USE_STD_SIGNATURE_HANDLER)
1062
1063 return ret;
1064}

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales