Digitally Sign PDF Files - Java 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
52import java.util.Locale;
53import java.io.IOException;
54import com.pdftron.common.PDFNetException;
55import com.pdftron.pdf.annots.TextWidget;
56import com.pdftron.pdf.annots.SignatureWidget;
57import com.pdftron.pdf.FieldIterator;
58import com.pdftron.pdf.DigitalSignatureFieldIterator;
59import com.pdftron.pdf.Field;
60import com.pdftron.pdf.Image;
61import com.pdftron.pdf.PDFDoc;
62import com.pdftron.pdf.PDFNet;
63import com.pdftron.pdf.Page;
64import com.pdftron.pdf.Rect;
65import com.pdftron.pdf.Date;
66import com.pdftron.pdf.DigitalSignatureField;
67import com.pdftron.pdf.VerificationOptions;
68import com.pdftron.pdf.VerificationResult;
69import com.pdftron.pdf.TrustVerificationResult;
70import com.pdftron.crypto.DigestAlgorithm;
71import com.pdftron.pdf.DisallowedChange;
72import com.pdftron.sdf.Obj;
73import com.pdftron.sdf.SignatureHandler;
74import com.pdftron.sdf.SDFDoc;
75import com.pdftron.filters.FilterReader;
76import com.pdftron.filters.MappedFile;
77import com.pdftron.crypto.X509Certificate;
78import com.pdftron.crypto.X501AttributeTypeAndValue;
79import com.pdftron.crypto.X501DistinguishedName;
80import com.pdftron.crypto.ObjectIdentifier;
81import com.pdftron.pdf.TimestampingConfiguration;
82import com.pdftron.pdf.TimestampingResult;
83import com.pdftron.pdf.EmbeddedTimestampVerificationResult;
84public class DigitalSignaturesTest
85{
86 public static boolean verifySimple(String in_docpath, String in_public_key_file_path) throws PDFNetException
87 {
88 PDFDoc doc = new PDFDoc(in_docpath);
89 System.out.println("==========");
90 VerificationOptions opts = new VerificationOptions(VerificationOptions.SecurityLevel.e_compatibility_and_archiving);
91
92 // Add trust root to store of trusted certificates contained in VerificationOptions.
93 opts.addTrustedCertificate(in_public_key_file_path,
94 VerificationOptions.CertificateTrustFlag.e_default_trust.value | VerificationOptions.CertificateTrustFlag.e_certification_trust.value);
95
96 PDFDoc.SignaturesVerificationStatus result = doc.verifySignedDigitalSignatures(opts);
97
98 switch (result)
99 {
100 case e_unsigned:
101 System.out.println("Document has no signed signature fields.");
102 return false;
103 /*e_failure == bad doc status, digest status, or permissions status
104 (i.e. does not include trust issues, because those are flaky due to being network/config-related)*/
105 case e_failure:
106 System.out.println("Hard failure in verification on at least one signature.");
107 return false;
108 case e_untrusted:
109 System.out.println("Could not verify trust for at least one signature.");
110 return false;
111 case e_unsupported:
112 /*If necessary, call GetUnsupportedFeatures on VerificationResult to check which
113 unsupported features were encountered (requires verification using 'detailed' APIs) */
114 System.out.println("At least one signature contains unsupported features.");
115 return false;
116 // unsigned sigs skipped; parts of document may be unsigned (check GetByteRanges on signed sigs to find out)
117 case e_verified:
118 System.out.println("All signed signatures in document verified.");
119 return true;
120 default:
121 System.err.println("unrecognized document verification status");
122 assert(false);
123 }
124 return false;
125 }
126
127 public static boolean verifyAllAndPrint(String in_docpath, String in_public_key_file_path) throws PDFNetException
128 {
129 PDFDoc doc = new PDFDoc(in_docpath);
130 System.out.println("==========");
131 VerificationOptions opts = new VerificationOptions(VerificationOptions.SecurityLevel.e_compatibility_and_archiving);
132
133 // Add trust root to store of trusted certificates contained in VerificationOptions.
134 opts.addTrustedCertificate(in_public_key_file_path,
135 VerificationOptions.CertificateTrustFlag.e_default_trust.value | VerificationOptions.CertificateTrustFlag.e_certification_trust.value);
136
137 // Iterate over the signatures and verify all of them.
138 DigitalSignatureFieldIterator digsig_fitr = doc.getDigitalSignatureFieldIterator();
139 boolean verification_status = true;
140 for (; digsig_fitr.hasNext(); )
141 {
142 DigitalSignatureField curr = digsig_fitr.next();
143 VerificationResult result = curr.verify(opts);
144 if (result.getVerificationStatus())
145 {
146 System.out.print("Signature verified, ");
147 }
148 else
149 {
150 System.out.print("Signature verification failed, ");
151 verification_status = false;
152 }
153 System.out.println(String.format(Locale.US, "objnum: %d", curr.getSDFObj().getObjNum()));
154
155 switch (result.getDigestAlgorithm())
156 {
157 case e_sha1:
158 System.out.println("Digest algorithm: SHA-1");
159 break;
160 case e_sha256:
161 System.out.println("Digest algorithm: SHA-256");
162 break;
163 case e_sha384:
164 System.out.println("Digest algorithm: SHA-384");
165 break;
166 case e_sha512:
167 System.out.println("Digest algorithm: SHA-512");
168 break;
169 case e_ripemd160:
170 System.out.println("Digest algorithm: RIPEMD-160");
171 break;
172 case e_unknown_digest_algorithm:
173 System.out.println("Digest algorithm: unknown");
174 break;
175 default:
176 System.err.println("unrecognized digest algorithm");
177 assert(false);
178 }
179 System.out.println(String.format("Detailed verification result: \n\t%s\n\t%s\n\t%s\n\t%s",
180 result.getDocumentStatusAsString(),
181 result.getDigestStatusAsString(),
182 result.getTrustStatusAsString(),
183 result.getPermissionsStatusAsString()));
184
185
186 DisallowedChange[] changes = result.getDisallowedChanges();
187 for (DisallowedChange it2 : changes)
188 {
189 System.out.println(String.format(Locale.US, "\tDisallowed change: %s, objnum: %d", it2.getTypeAsString(), it2.getObjNum()));
190 }
191
192 // Get and print all the detailed trust-related results, if they are available.
193 if (result.hasTrustVerificationResult())
194 {
195 TrustVerificationResult trust_verification_result = result.getTrustVerificationResult();
196 System.out.println(trust_verification_result.wasSuccessful() ? "Trust verified." : "Trust not verifiable.");
197 System.out.println(trust_verification_result.getResultString());
198
199 long time_of_verification = trust_verification_result.getTimeOfTrustVerification();
200 switch (trust_verification_result.getTimeOfTrustVerificationEnum())
201 {
202 case e_current:
203 System.out.println(String.format(Locale.US, "Trust verification attempted with respect to current time (as epoch time): %d", time_of_verification));
204 break;
205 case e_signing:
206 System.out.println(String.format(Locale.US, "Trust verification attempted with respect to signing time (as epoch time): %d", time_of_verification));
207 break;
208 case e_timestamp:
209 System.out.println(String.format(Locale.US, "Trust verification attempted with respect to secure embedded timestamp (as epoch time): %d", time_of_verification));
210 break;
211 default:
212 System.err.println("unrecognized time enum value");
213 assert(false);
214 }
215
216 if(trust_verification_result.getCertPath().length == 0 )
217 {
218 System.out.println("Could not print certificate path.");
219 }
220 else
221 {
222 System.out.println("Certificate path:");
223 X509Certificate[] cert_path = trust_verification_result.getCertPath();
224 for (int j = 0; j < cert_path.length; j++)
225 {
226 System.out.println("\tCertificate:");
227 X509Certificate full_cert = cert_path[j];
228 System.out.println("\t\tIssuer names:");
229
230 X501AttributeTypeAndValue[] issuer_dn = full_cert.getIssuerField().getAllAttributesAndValues();
231 for (int i = 0; i < issuer_dn.length; i++)
232 {
233 System.out.println("\t\t\t" + issuer_dn[i].getStringValue());
234 }
235 System.out.println("\t\tSubject names:");
236 X501AttributeTypeAndValue[] subject_dn = full_cert.getSubjectField().getAllAttributesAndValues();
237 for (int i = 0; i < subject_dn.length; i++)
238 {
239 System.out.println("\t\t\t" + subject_dn[i].getStringValue());
240 }
241 System.out.println("\t\tExtensions:");
242 for (int i = 0; i < full_cert.getExtensions().length; i++)
243 {
244 System.out.println("\t\t\t" + full_cert.getExtensions()[i].toString());
245 }
246 }
247 }
248 }
249 else
250 {
251 System.out.println("No detailed trust verification result available.");
252 }
253
254 String[] unsupported_features = result.getUnsupportedFeatures();
255 if (unsupported_features.length > 0)
256 {
257 System.out.println("Unsupported features:");
258
259 for (String unsupported_feature : unsupported_features)
260 {
261 System.out.println("\t" + unsupported_feature);
262 }
263 }
264 System.out.println("==========");
265 }
266
267 return verification_status;
268 }
269
270 public static void certifyPDF(String in_docpath,
271 String in_cert_field_name,
272 String in_private_key_file_path,
273 String in_keyfile_password,
274 String in_appearance_image_path,
275 String in_outpath) throws PDFNetException
276 {
277 System.out.println("================================================================================");
278 System.out.println("Certifying PDF document");
279
280 // Open an existing PDF
281 PDFDoc doc = new PDFDoc(in_docpath);
282
283 if (doc.hasSignatures())
284 {
285 System.out.println("PDFDoc has signatures");
286 }
287 else
288 {
289 System.out.println("PDFDoc has no signatures");
290 }
291
292 Page page1 = doc.getPage(1);
293
294 // Create a text field that we can lock using the field permissions feature.
295 TextWidget annot1 = TextWidget.create(doc, new Rect(143, 440, 350, 460), "asdf_test_field");
296 page1.annotPushBack(annot1);
297
298 /* Create a new signature form field in the PDFDoc. The name argument is optional;
299 leaving it empty causes it to be auto-generated. However, you may need the name for later.
300 Acrobat doesn't show digsigfield in side panel if it's without a widget. Using a
301 Rect with 0 width and 0 height, or setting the NoPrint/Invisible flags makes it invisible. */
302 DigitalSignatureField certification_sig_field = doc.createDigitalSignatureField(in_cert_field_name);
303 SignatureWidget widgetAnnot = SignatureWidget.create(doc, new Rect(143, 287, 219, 306), certification_sig_field);
304 page1.annotPushBack(widgetAnnot);
305
306 // (OPTIONAL) Add an appearance to the signature field.
307 Image img = Image.create(doc, in_appearance_image_path);
308 widgetAnnot.createSignatureAppearance(img);
309
310 // Prepare the document locking permission level. It will be applied upon document certification.
311 System.out.println("Adding document permissions.");
312 certification_sig_field.setDocumentPermissions(DigitalSignatureField.DocumentPermissions.e_annotating_formfilling_signing_allowed);
313
314 // Prepare to lock the text field that we created earlier.
315 System.out.println("Adding field permissions.");
316 String[] fields_to_lock = {"asdf_test_field"};
317 certification_sig_field.setFieldPermissions(DigitalSignatureField.FieldPermissions.e_include, fields_to_lock);
318
319 certification_sig_field.certifyOnNextSave(in_private_key_file_path, in_keyfile_password);
320
321 // (OPTIONAL) Add more information to the signature dictionary.
322 certification_sig_field.setLocation("Vancouver, BC");
323 certification_sig_field.setReason("Document certification.");
324 certification_sig_field.setContactInfo("www.pdftron.com");
325
326 // Save the PDFDoc. Once the method below is called, PDFNet will also sign the document using the information provided.
327 doc.save(in_outpath, SDFDoc.SaveMode.NO_FLAGS, null);
328
329 System.out.println("================================================================================");
330 }
331
332 public static void signPDF(String in_docpath,
333 String in_approval_field_name,
334 String in_private_key_file_path,
335 String in_keyfile_password,
336 String in_appearance_img_path,
337 String in_outpath) throws PDFNetException
338 {
339 System.out.println("================================================================================");
340 System.out.println("Signing PDF document");
341
342 // Open an existing PDF
343 PDFDoc doc = new PDFDoc(in_docpath);
344
345 // Retrieve the unsigned approval signature field.
346 Field found_approval_field = doc.getField(in_approval_field_name);
347 DigitalSignatureField found_approval_signature_digsig_field = new DigitalSignatureField(found_approval_field);
348
349 // (OPTIONAL) Add an appearance to the signature field.
350 Image img = Image.create(doc, in_appearance_img_path);
351 SignatureWidget found_approval_signature_widget = new SignatureWidget(found_approval_field.getSDFObj());
352 found_approval_signature_widget.createSignatureAppearance(img);
353
354 // Prepare the signature and signature handler for signing.
355 found_approval_signature_digsig_field.signOnNextSave(in_private_key_file_path, in_keyfile_password);
356
357 // The actual approval signing will be done during the following incremental save operation.
358 doc.save(in_outpath, SDFDoc.SaveMode.INCREMENTAL, null);
359
360 System.out.println("================================================================================");
361 }
362
363 public static void clearSignature(String in_docpath,
364 String in_digsig_field_name,
365 String in_outpath) throws PDFNetException
366 {
367 System.out.println("================================================================================");
368 System.out.println("Clearing certification signature");
369
370 PDFDoc doc = new PDFDoc(in_docpath);
371
372 DigitalSignatureField digsig = new DigitalSignatureField(doc.getField(in_digsig_field_name));
373
374 System.out.println("Clearing signature: " + in_digsig_field_name);
375 digsig.clearSignature();
376
377 if (!digsig.hasCryptographicSignature())
378 {
379 System.out.println("Cryptographic signature cleared properly.");
380 }
381
382 // Save incrementally so as to not invalidate other signatures from previous saves.
383 doc.save(in_outpath, SDFDoc.SaveMode.INCREMENTAL, null);
384
385 System.out.println("================================================================================");
386 }
387
388 public static void printSignaturesInfo(String in_docpath) throws PDFNetException
389 {
390 System.out.println("================================================================================");
391 System.out.println("Reading and printing digital signature information");
392
393 PDFDoc doc = new PDFDoc(in_docpath);
394 if (!doc.hasSignatures())
395 {
396 System.out.println("Doc has no signatures.");
397 System.out.println("================================================================================");
398 return;
399 }
400 else
401 {
402 System.out.println("Doc has signatures.");
403 }
404
405
406 for (FieldIterator fitr = doc.getFieldIterator(); fitr.hasNext(); )
407 {
408 Field current = fitr.next();
409 if (current.isLockedByDigitalSignature())
410 {
411 System.out.println("==========\nField locked by a digital signature");
412 }
413 else
414 {
415 System.out.println("==========\nField not locked by a digital signature");
416 }
417
418 System.out.println("Field name: " + current.getName());
419 System.out.println("==========");
420 }
421
422 System.out.println("====================\nNow iterating over digital signatures only.\n====================");
423
424 DigitalSignatureFieldIterator digsig_fitr = doc.getDigitalSignatureFieldIterator();
425 for (; digsig_fitr.hasNext(); )
426 {
427 DigitalSignatureField current = digsig_fitr.next();
428 System.out.println("==========");
429 System.out.println("Field name of digital signature: " + new Field(current.getSDFObj()).getName());
430
431 DigitalSignatureField digsigfield = current;
432 if (!digsigfield.hasCryptographicSignature())
433 {
434 System.out.println("Either digital signature field lacks a digital signature dictionary, " +
435 "or digital signature dictionary lacks a cryptographic Contents entry. " +
436 "Digital signature field is not presently considered signed.\n" +
437 "==========");
438 continue;
439 }
440
441 int cert_count = digsigfield.getCertCount();
442 System.out.println("Cert count: " + cert_count);
443 for (int i = 0; i < cert_count; ++i)
444 {
445 byte[] cert = digsigfield.getCert(i);
446 System.out.println("Cert #" + i + " size: " + cert.length);
447 }
448
449 DigitalSignatureField.SubFilterType subfilter = digsigfield.getSubFilter();
450
451 System.out.println("Subfilter type: " + subfilter.ordinal());
452
453 if (subfilter != DigitalSignatureField.SubFilterType.e_ETSI_RFC3161)
454 {
455 System.out.println("Signature's signer: " + digsigfield.getSignatureName());
456
457 Date signing_time = digsigfield.getSigningTime();
458 if (signing_time.isValid())
459 {
460 System.out.println("Signing time is valid.");
461 }
462
463 System.out.println("Location: " + digsigfield.getLocation());
464 System.out.println("Reason: " + digsigfield.getReason());
465 System.out.println("Contact info: " + digsigfield.getContactInfo());
466 }
467 else
468 {
469 System.out.println("SubFilter == e_ETSI_RFC3161 (DocTimeStamp; no signing info)");
470 }
471
472 if (digsigfield.hasVisibleAppearance())
473 {
474 System.out.println("Visible");
475 }
476 else
477 {
478 System.out.println("Not visible");
479 }
480
481 DigitalSignatureField.DocumentPermissions digsig_doc_perms = digsigfield.getDocumentPermissions();
482 String[] locked_fields = digsigfield.getLockedFields();
483 for (String it : locked_fields)
484 {
485 System.out.println("This digital signature locks a field named: " + it);
486 }
487
488 switch (digsig_doc_perms)
489 {
490 case e_no_changes_allowed:
491 System.out.println("No changes to the document can be made without invalidating this digital signature.");
492 break;
493 case e_formfilling_signing_allowed:
494 System.out.println("Page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.");
495 break;
496 case e_annotating_formfilling_signing_allowed:
497 System.out.println("Annotating, page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.");
498 break;
499 case e_unrestricted:
500 System.out.println("Document not restricted by this digital signature.");
501 break;
502 default:
503 System.err.println("Unrecognized digital signature document permission level.");
504 assert(false);
505 }
506 System.out.println("==========");
507 }
508
509 System.out.println("================================================================================");
510 }
511
512 public static void CustomSigningAPI(String doc_path,
513 String cert_field_name,
514 String private_key_file_path,
515 String keyfile_password,
516 String public_key_file_path,
517 String appearance_image_path,
518 DigestAlgorithm digest_algorithm_type,
519 boolean PAdES_signing_mode,
520 String output_path) throws PDFNetException, IOException
521 {
522 System.out.println("================================================================================");
523 System.out.println("Custom signing PDF document");
524
525 PDFDoc doc = new PDFDoc(doc_path);
526
527 Page page1 = doc.getPage(1);
528
529 DigitalSignatureField digsig_field = doc.createDigitalSignatureField(cert_field_name);
530 SignatureWidget widgetAnnot = SignatureWidget.create(doc, new Rect(143, 287, 219, 306), digsig_field);
531 page1.annotPushBack(widgetAnnot);
532
533 // (OPTIONAL) Add an appearance to the signature field.
534 Image img = Image.create(doc, appearance_image_path);
535 widgetAnnot.createSignatureAppearance(img);
536
537 // Create a digital signature dictionary inside the digital signature field, in preparation for signing.
538 digsig_field.createSigDictForCustomSigning("Adobe.PPKLite",
539 PAdES_signing_mode ? DigitalSignatureField.SubFilterType.e_ETSI_CAdES_detached : DigitalSignatureField.SubFilterType.e_adbe_pkcs7_detached,
540 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.
541 // ... or, if you want to apply a certification signature, use CreateSigDictForCustomCertification instead.
542
543 // (OPTIONAL) Set the signing time in the signature dictionary, if no secure embedded timestamping support is available from your signing provider.
544 Date current_date = new Date();
545 current_date.setCurrentTime();
546 digsig_field.setSigDictTimeOfSigning(current_date);
547
548 doc.save(output_path, SDFDoc.SaveMode.INCREMENTAL, null);
549
550 // Digest the relevant bytes of the document in accordance with ByteRanges surrounding the signature.
551 byte[] pdf_digest = digsig_field.calculateDigest(digest_algorithm_type);
552
553 X509Certificate signer_cert = new X509Certificate(public_key_file_path);
554
555 // Optionally, you can add a custom signed attribute at this point, such as one of the PAdES ESS attributes.
556 // The function we provide takes care of generating the correct PAdES ESS attribute depending on your digest algorithm.
557 byte[] pades_versioned_ess_signing_cert_attribute = DigitalSignatureField.generateESSSigningCertPAdESAttribute(signer_cert, digest_algorithm_type);
558
559 // Generate the signedAttrs component of CMS, passing any optional custom signedAttrs (e.g. PAdES ESS).
560 // The signedAttrs are certain attributes that become protected by their inclusion in the signature.
561 byte[] signedAttrs = DigitalSignatureField.generateCMSSignedAttributes(pdf_digest, pades_versioned_ess_signing_cert_attribute);
562
563 // Calculate the digest of the signedAttrs (i.e. not the PDF digest, this time).
564 byte[] signedAttrs_digest = DigestAlgorithm.calculateDigest(digest_algorithm_type, signedAttrs);
565
566 //////////////////////////// custom digest signing starts ////////////////////////////
567 // At this point, you can sign the digest (for example, with HSM). We use our own SignDigest function instead here as an example,
568 // which you can also use for your purposes if necessary as an alternative to the handler/callback APIs (i.e. Certify/SignOnNextSave).
569 byte[] signature_value = DigestAlgorithm.signDigest(
570 signedAttrs_digest,
571 digest_algorithm_type,
572 private_key_file_path,
573 keyfile_password);
574 //////////////////////////// custom digest signing ends //////////////////////////////
575
576 // Then, load all your chain certificates into a container of X509Certificate.
577 X509Certificate[] chain_certs = {};
578
579 // Then, create ObjectIdentifiers for the algorithms you have used.
580 // Here we use digest_algorithm_type (usually SHA256) for hashing, and RSAES-PKCS1-v1_5 (specified in the private key) for signing.
581 ObjectIdentifier digest_algorithm_oid = new ObjectIdentifier(digest_algorithm_type);
582 ObjectIdentifier signature_algorithm_oid = new ObjectIdentifier(ObjectIdentifier.Predefined.RSA_encryption_PKCS1);
583
584 // Then, put the CMS signature components together.
585 byte[] cms_signature = DigitalSignatureField.generateCMSSignature(
586 signer_cert, chain_certs, digest_algorithm_oid, signature_algorithm_oid,
587 signature_value, signedAttrs);
588
589 // Write the signature to the document.
590 doc.saveCustomSignature(cms_signature, digsig_field, output_path);
591
592 System.out.println("================================================================================");
593 }
594
595 public static boolean timestampAndEnableLTV(String doc_path,
596 String tsa_url,
597 String trusted_cert_path,
598 String appearance_img_path,
599 String output_path) throws PDFNetException
600 {
601 PDFDoc doc = new PDFDoc(doc_path);
602 DigitalSignatureField doctimestamp_signature_field = doc.createDigitalSignatureField();
603 TimestampingConfiguration tst_config = new TimestampingConfiguration(tsa_url);
604 VerificationOptions opts = new VerificationOptions(VerificationOptions.SecurityLevel.e_compatibility_and_archiving);
605 /* It is necessary to add to the VerificationOptions a trusted root certificate corresponding to
606 the chain used by the timestamp authority to sign the timestamp token, in order for the timestamp
607 response to be verifiable during DocTimeStamp signing. It is also necessary in the context of this
608 function to do this for the later LTV section, because one needs to be able to verify the DocTimeStamp
609 in order to enable LTV for it, and we re-use the VerificationOptions opts object in that part. */
610 opts.addTrustedCertificate(trusted_cert_path);
611 /* By default, we only check online for revocation of certificates using the newer and lighter
612 OCSP protocol as opposed to CRL, due to lower resource usage and greater reliability. However,
613 it may be necessary to enable online CRL revocation checking in order to verify some timestamps
614 (i.e. those that do not have an OCSP responder URL for all non-trusted certificates). */
615 opts.enableOnlineCRLRevocationChecking(true);
616
617 SignatureWidget widgetAnnot = SignatureWidget.create(doc, new Rect(0, 100, 200, 150), doctimestamp_signature_field);
618 doc.getPage(1).annotPushBack(widgetAnnot);
619
620 // (OPTIONAL) Add an appearance to the signature field.
621 Image img = Image.create(doc, appearance_img_path);
622 widgetAnnot.createSignatureAppearance(img);
623
624 System.out.println("Testing timestamping configuration.");
625 TimestampingResult config_result = tst_config.testConfiguration(opts);
626 if (config_result.getStatus())
627 {
628 System.out.println("Success: timestamping configuration usable. Attempting to timestamp.");
629 }
630 else
631 {
632 // Print details of timestamping failure.
633 System.out.println(config_result.getString());
634 if (config_result.hasResponseVerificationResult())
635 {
636 EmbeddedTimestampVerificationResult tst_result = config_result.getResponseVerificationResult();
637 System.out.println(String.format("CMS digest status: %s", tst_result.getCMSDigestStatusAsString()));
638 System.out.println(String.format("Message digest status: %s", tst_result.getMessageImprintDigestStatusAsString()));
639 System.out.println(String.format("Trust status: %s", tst_result.getTrustStatusAsString()));
640 }
641 return false;
642 }
643
644 doctimestamp_signature_field.timestampOnNextSave(tst_config, opts);
645
646 // Save/signing throws if timestamping fails.
647 doc.save(output_path, SDFDoc.SaveMode.INCREMENTAL, null);
648
649 System.out.println("Timestamping successful. Adding LTV information for DocTimeStamp signature.");
650
651 // Add LTV information for timestamp signature to document.
652 VerificationResult timestamp_verification_result = doctimestamp_signature_field.verify(opts);
653 if (!doctimestamp_signature_field.enableLTVOfflineVerification(timestamp_verification_result))
654 {
655 System.out.println("Could not enable LTV for DocTimeStamp.");
656 return false;
657 }
658 doc.save(output_path, SDFDoc.SaveMode.INCREMENTAL, null);
659 System.out.println("Added LTV information for DocTimeStamp signature successfully.");
660
661 return true;
662 }
663
664 public static void main(String[] args)
665 {
666 // Initialize PDFNet
667 PDFNet.initialize(PDFTronLicense.Key());
668
669 boolean result = true;
670 String input_path = "../../TestFiles/";
671 String output_path = "../../TestFiles/Output/";
672
673
674 //////////////////// TEST 0:
675 /* Create an approval signature field that we can sign after certifying.
676 (Must be done before calling CertifyOnNextSave/SignOnNextSave/WithCustomHandler.) */
677 try (PDFDoc doc = new PDFDoc(input_path + "waiver.pdf"))
678 {
679 DigitalSignatureField approval_signature_field = doc.createDigitalSignatureField("PDFTronApprovalSig");
680 SignatureWidget widgetAnnotApproval = SignatureWidget.create(doc, new Rect(300, 287, 376, 306), approval_signature_field);
681 Page page1 = doc.getPage(1);
682 page1.annotPushBack(widgetAnnotApproval);
683 doc.save(output_path + "waiver_withApprovalField_output.pdf", SDFDoc.SaveMode.REMOVE_UNUSED, null);
684 }
685 catch (Exception e)
686 {
687 System.err.println(e.getMessage());
688 e.printStackTrace(System.err);
689 result = false;
690 }
691
692 //////////////////// TEST 1: certify a PDF.
693 try
694 {
695 certifyPDF(input_path + "waiver_withApprovalField.pdf",
696 "PDFTronCertificationSig",
697 input_path + "pdftron.pfx",
698 "password",
699 input_path + "pdftron.bmp",
700 output_path + "waiver_withApprovalField_certified_output.pdf");
701 printSignaturesInfo(output_path + "waiver_withApprovalField_certified_output.pdf");
702 }
703 catch (Exception e)
704 {
705 System.err.println(e.getMessage());
706 e.printStackTrace(System.err);
707 result = false;
708 }
709
710 //////////////////// TEST 2: approval-sign an existing, unsigned signature field in a PDF that already has a certified signature field.
711 try
712 {
713 signPDF(input_path + "waiver_withApprovalField_certified.pdf",
714 "PDFTronApprovalSig",
715 input_path + "pdftron.pfx",
716 "password",
717 input_path + "signature.jpg",
718 output_path + "waiver_withApprovalField_certified_approved_output.pdf");
719 printSignaturesInfo(output_path + "waiver_withApprovalField_certified_approved_output.pdf");
720 }
721 catch (Exception e)
722 {
723 System.err.println(e.getMessage());
724 e.printStackTrace(System.err);
725 result = false;
726 }
727
728 //////////////////// TEST 3: Clear a certification from a document that is certified and has an approval signature.
729 try
730 {
731 clearSignature(input_path + "waiver_withApprovalField_certified_approved.pdf",
732 "PDFTronCertificationSig",
733 output_path + "waiver_withApprovalField_certified_approved_certcleared_output.pdf");
734 printSignaturesInfo(output_path + "waiver_withApprovalField_certified_approved_certcleared_output.pdf");
735 }
736 catch (Exception e)
737 {
738 System.err.println(e.getMessage());
739 e.printStackTrace(System.err);
740 result = false;
741 }
742
743 //////////////////// TEST 4: Verify a document's digital signatures.
744 try
745 {
746 if (!verifyAllAndPrint(input_path + "waiver_withApprovalField_certified_approved.pdf",
747 input_path + "pdftron.cer"))
748 {
749 result = false;
750 }
751 }
752 catch (Exception e)
753 {
754 System.err.println(e.getMessage());
755 e.printStackTrace(System.err);
756 result = false;
757 }
758
759 //////////////////// TEST 5: Verify a document's digital signatures in a simple fashion using the document API.
760 try
761 {
762 if (!verifySimple(input_path + "waiver_withApprovalField_certified_approved.pdf",
763 input_path + "pdftron.cer"))
764 {
765 result = false;
766 }
767 }
768 catch (Exception e)
769 {
770 System.err.println(e.getMessage());
771 e.printStackTrace(System.err);
772 result = false;
773
774 }
775
776 //////////////////// TEST 6: Custom signing API.
777 // The Apryse custom signing API is a set of APIs related to cryptographic digital signatures
778 // which allows users to customize the process of signing documents. Among other things, this
779 // includes the capability to allow for easy integration of PDF-specific signing-related operations
780 // with access to Hardware Security Module (HSM) tokens/devices, access to cloud keystores, access
781 // to system keystores, etc.
782 try
783 {
784 CustomSigningAPI(input_path + "waiver.pdf",
785 "PDFTronApprovalSig",
786 input_path + "pdftron.pfx",
787 "password",
788 input_path + "pdftron.cer",
789 input_path + "signature.jpg",
790 DigestAlgorithm.e_sha256,
791 true,
792 output_path + "waiver_custom_signed.pdf");
793 }
794 catch (Exception e)
795 {
796 System.err.println(e.getMessage());
797 e.printStackTrace(System.err);
798 result = false;
799 }
800
801 //////////////////// TEST 7: Timestamp a document, then add Long Term Validation (LTV) information for the DocTimeStamp.
802 // try
803 // {
804 // // Replace YOUR_URL_OF_TSA with the timestamp authority (TSA) URL to use during timestamping.
805 // // For example, as of July 2024, http://timestamp.globalsign.com/tsa/r6advanced1 was usable.
806 // // Note that this url may not work in the future. A reliable solution requires using your own TSA.
807 // String tsa_url = "YOUR_URL_OF_TSA";
808 // if (tsa_url == "YOUR_URL_OF_TSA")
809 // {
810 // throw new Exception("Error: The URL of your timestamp authority was not specified.");
811 // }
812 //
813 // // Replace YOUR_CERTIFICATE with the trusted root certificate corresponding to the chain used by the timestamp authority.
814 // // For example, as of July 2024, https://secure.globalsign.com/cacert/gstsacasha384g4.crt was usable.
815 // // Note that this certificate may not work in the future. A reliable solution requires using your own TSA certificate.
816 // String trusted_cert_path = "YOUR_CERTIFICATE";
817 // if (trusted_cert_path == "YOUR_CERTIFICATE")
818 // {
819 // throw new Exception("Error: The path to your timestamp authority trusted root certificate was not specified.");
820 // }
821 //
822 // if (!timestampAndEnableLTV(input_path + "waiver.pdf",
823 // tsa_url,
824 // trusted_cert_path,
825 // input_path + "signature.jpg",
826 // output_path+ "waiver_DocTimeStamp_LTV.pdf"))
827 // {
828 // result = false;
829 // }
830 // }
831 // catch (Exception e)
832 // {
833 // System.err.println(e.getMessage());
834 // e.printStackTrace(System.err);
835 // result = false;
836 //
837 // }
838
839 //////////////////// End of tests. ////////////////////
840
841 if (result)
842 {
843 System.out.println("Tests successful.\n==========");
844 }
845 else
846 {
847 System.out.println("Tests FAILED!!!\n==========");
848 }
849 PDFNet.terminate();
850 }
851}

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales