DigitalSignatures

Sample C# code to use Apryse SDK's high-level digital signature API for digitally signing and/or certifying PDF files. 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//
4
5//----------------------------------------------------------------------------------------------------------------------
6// This sample demonstrates the basic usage of the high-level digital signatures API in PDFNet.
7//
8// The following steps reflect typical intended usage of the digital signatures API:
9//
10// 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)).
11//
12// 1. EITHER:
13// (a) Call doc.CreateDigitalSignatureField, optionally providing a name. You receive a DigitalSignatureField.
14// -OR-
15// (b) If you didn't just create the digital signature field that you want to sign/certify, find the existing one within the
16// document by using PDFDoc.DigitalSignatureFieldIterator or by using PDFDoc.GetField to get it by its fully qualified name.
17//
18// 2. Create a signature widget annotation, and pass the DigitalSignatureField that you just created or found.
19// If you want it to be visible, provide a Rect argument with a non-zero width or height, and don't set the
20// NoView and Hidden flags. [Optionally, add an appearance to the annotation when you wish to sign/certify.]
21//
22// [3. (OPTIONAL) Add digital signature restrictions to the document using the field modification permissions (SetFieldPermissions)
23// or document modification permissions functions (SetDocumentPermissions) of DigitalSignatureField. These features disallow
24// certain types of changes to be made to the document without invalidating the cryptographic digital signature once it
25// is signed.]
26//
27// 4. Call either CertifyOnNextSave or SignOnNextSave. There are three overloads for each one (six total):
28// a. Taking a PKCS #12 keyfile path and its password
29// b. Taking a buffer containing a PKCS #12 private keyfile and its password
30// c. Taking a unique identifier of a signature handler registered with the PDFDoc. This overload is to be used
31// in the following fashion:
32// i) Extend and implement a new SignatureHandler. The SignatureHandler will be used to add or
33// validate/check a digital signature.
34// ii) Create an instance of the implemented SignatureHandler and register it with PDFDoc with
35// pdfdoc.AddSignatureHandler(). The method returns a SignatureHandlerId.
36// iii) Call SignOnNextSaveWithCustomHandler/CertifyOnNextSaveWithCustomHandler with the SignatureHandlerId.
37// NOTE: It is only possible to sign/certify one signature per call to the Save function.
38//
39// 5. Call pdfdoc.Save(). This will also create the digital signature dictionary and write a cryptographic signature to it.
40// IMPORTANT: If there are already signed/certified digital signature(s) in the document, you must save incrementally
41// so as to not invalidate the other signature(s).
42//
43// Additional processing can be done before document is signed. For example, UseSignatureHandler() returns an instance
44// of SDF dictionary which represents the signature dictionary (or the /V entry of the form field). This can be used to
45// add additional information to the signature dictionary (e.g. Name, Reason, Location, etc.).
46//
47// Although the steps above describes extending the SignatureHandler class, this sample demonstrates the use of
48// StdSignatureHandler (a built-in SignatureHandler in PDFNet) to sign a PDF file.
49//----------------------------------------------------------------------------------------------------------------------
50
51// In order to use .NET Framework's Cryptography library, define "USE_DOTNET_CRYPTO" and then add System.Security to
52// references list.
53
54using System;
55using System.Collections.Generic;
56using System.IO;
57#if USE_DOTNET_CRYPTO
58using System.Security.Cryptography;
59using System.Security.Cryptography.Pkcs;
60using System.Security.Cryptography.X509Certificates;
61#endif // USE_DOTNET_CRYPTO
62
63using pdftron;
64using pdftron.Common;
65using pdftron.PDF;
66using pdftron.PDF.Annots;
67using pdftron.SDF;
68using pdftron.Crypto;
69
70namespace DigitalSignaturesTestCS
71{
72 //////////////////// Here follows an example of how to implement a custom signature handler. //////////
73#if USE_DOTNET_CRYPTO
74 class DotNetCryptoSignatureHandler : SignatureHandler
75 {
76 private List<byte> m_data;
77 private string m_signingCert;
78 private string m_certPassword;
79
80 public DotNetCryptoSignatureHandler(string signingCert, string password)
81 {
82 m_signingCert = signingCert;
83 m_certPassword = password;
84 m_data = new List<byte>();
85 }
86
87 public override void AppendData(byte[] data)
88 {
89 m_data.AddRange(data);
90 }
91
92 public override bool Reset()
93 {
94 m_data.Clear();
95 return (true);
96 }
97
98 public override byte[] CreateSignature()
99 {
100 try {
101 ContentInfo ci = new ContentInfo(m_data.ToArray());
102 SignedCms sc = new SignedCms(ci, true);
103 X509Certificate2 cert = new X509Certificate2(m_signingCert, m_certPassword);
104 CmsSigner cs = new CmsSigner();
105 cs.Certificate = cert;
106 cs.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");//SHA256
107 sc.ComputeSignature(cs);
108 byte[] sig = sc.Encode();
109 return (sig);
110 }
111 catch (Exception e) {
112 Console.Error.WriteLine(e);
113 }
114 return (null);
115 }
116
117 public override string GetName()
118 {
119 return ("Adobe.PPKLite");
120 }
121 }
122#endif // USE_DOTNET_CRYPTO
123 ////////// End of the DotNetCryptoSignatureHandler custom handler code. ////////////////////
124
125 class Class1
126 {
127 static string input_path = "../../../../TestFiles/";
128 static string output_path = "../../../../TestFiles/Output/";
129
130 static bool VerifySimple (string in_docpath, string in_public_key_file_path)
131 {
132 using (PDFDoc doc = new PDFDoc(in_docpath))
133 {
134 Console.WriteLine("==========");
135 VerificationOptions opts = new VerificationOptions(VerificationOptions.SignatureVerificationSecurityLevel.e_compatibility_and_archiving);
136
137 // Add trust root to store of trusted certificates contained in VerificationOptions.
138 opts.AddTrustedCertificate(in_public_key_file_path,
139 (ushort)(VerificationOptions.CertificateTrustFlag.e_default_trust | VerificationOptions.CertificateTrustFlag.e_certification_trust));
140
141 PDFDoc.SignaturesVerificationStatus result = doc.VerifySignedDigitalSignatures(opts);
142 switch (result)
143 {
144 case PDFDoc.SignaturesVerificationStatus.e_unsigned:
145 Console.WriteLine("Document has no signed signature fields.");
146 return false;
147 /* e_failure == bad doc status, digest status, or permissions status
148 (i.e. does not include trust issues, because those are flaky due to being network/config-related) */
149 case PDFDoc.SignaturesVerificationStatus.e_failure:
150 Console.WriteLine("Hard failure in verification on at least one signature.");
151 return false;
152 case PDFDoc.SignaturesVerificationStatus.e_untrusted:
153 Console.WriteLine("Could not verify trust for at least one signature.");
154 return false;
155 case PDFDoc.SignaturesVerificationStatus.e_unsupported:
156 /* If necessary, call GetUnsupportedFeatures on VerificationResult to check which
157 unsupported features were encountered (requires verification using 'detailed' APIs) */
158 Console.WriteLine("At least one signature contains unsupported features.");
159 return false;
160 // unsigned sigs skipped; parts of document may be unsigned (check GetByteRanges on signed sigs to find out)
161 case PDFDoc.SignaturesVerificationStatus.e_verified:
162 Console.WriteLine("All signed signatures in document verified.");
163 return true;
164 default:
165 throw new Exception("unrecognized document verification status");
166 }
167 }
168 }
169
170 static bool VerifyAllAndPrint(string in_docpath, string in_public_key_file_path)
171 {
172 using (PDFDoc doc = new PDFDoc(in_docpath))
173 {
174 Console.WriteLine("==========");
175 VerificationOptions opts = new VerificationOptions(VerificationOptions.SignatureVerificationSecurityLevel.e_compatibility_and_archiving);
176
177 // Add trust root to store of trusted certificates contained in VerificationOptions.
178 opts.AddTrustedCertificate(in_public_key_file_path,
179 (ushort) (VerificationOptions.CertificateTrustFlag.e_default_trust | VerificationOptions.CertificateTrustFlag.e_certification_trust));
180
181 // Iterate over the signatures and verify all of them.
182 DigitalSignatureFieldIterator digsig_fitr = doc.GetDigitalSignatureFieldIterator();
183 bool verification_status = true;
184 for (; digsig_fitr.HasNext(); digsig_fitr.Next())
185 {
186 DigitalSignatureField curr = digsig_fitr.Current();
187 VerificationResult result = curr.Verify(opts);
188 if (result.GetVerificationStatus())
189 {
190 Console.Write("Signature verified, ");
191 }
192 else
193 {
194 Console.Write("Signature verification failed, ");
195 verification_status = false;
196 }
197 Console.WriteLine("objnum: {0}", curr.GetSDFObj().GetObjNum());
198
199 switch (result.GetDigestAlgorithm())
200 {
201 case DigestAlgorithm.Type.e_sha1:
202 Console.WriteLine("Digest algorithm: SHA-1");
203 break;
204 case DigestAlgorithm.Type.e_sha256:
205 Console.WriteLine("Digest algorithm: SHA-256");
206 break;
207 case DigestAlgorithm.Type.e_sha384:
208 Console.WriteLine("Digest algorithm: SHA-384");
209 break;
210 case DigestAlgorithm.Type.e_sha512:
211 Console.WriteLine("Digest algorithm: SHA-512");
212 break;
213 case DigestAlgorithm.Type.e_ripemd160:
214 Console.WriteLine("Digest algorithm: RIPEMD-160");
215 break;
216 case DigestAlgorithm.Type.e_unknown_digest_algorithm:
217 Console.WriteLine("Digest algorithm: unknown");
218 break;
219 default:
220 throw new Exception("unrecognized digest algorithm");
221 }
222 Console.WriteLine("Detailed verification result: \n\t{0}\n\t{1}\n\t{2}\n\t{3}",
223 result.GetDocumentStatusAsString(),
224 result.GetDigestStatusAsString(),
225 result.GetTrustStatusAsString(),
226 result.GetPermissionsStatusAsString());
227
228
229 DisallowedChange[] changes = result.GetDisallowedChanges();
230 foreach (DisallowedChange it2 in changes)
231 {
232 Console.WriteLine("\tDisallowed change: {0}, objnum: {1}", it2.GetTypeAsString(), it2.GetObjNum());
233 }
234
235 // Get and print all the detailed trust-related results, if they are available.
236 if (result.HasTrustVerificationResult())
237 {
238 TrustVerificationResult trust_verification_result = result.GetTrustVerificationResult();
239 Console.WriteLine(trust_verification_result.WasSuccessful() ? "Trust verified." : "Trust not verifiable.");
240 Console.WriteLine(trust_verification_result.GetResultString());
241
242 long time_of_verification = trust_verification_result.GetTimeOfTrustVerification();
243 switch (trust_verification_result.GetTimeOfTrustVerificationEnum())
244 {
245 case VerificationOptions.TimeMode.e_current:
246 Console.WriteLine("Trust verification attempted with respect to current time (as epoch time): {0}", time_of_verification);
247 break;
248 case VerificationOptions.TimeMode.e_signing:
249 Console.WriteLine("Trust verification attempted with respect to signing time (as epoch time): {0}", time_of_verification);
250 break;
251 case VerificationOptions.TimeMode.e_timestamp:
252 Console.WriteLine("Trust verification attempted with respect to secure embedded timestamp (as epoch time): {0}", time_of_verification);
253 break;
254 default:
255 throw new Exception("unrecognized time enum value");
256 }
257
258 if (trust_verification_result.GetCertPath().Length == 0)
259 {
260 Console.WriteLine("Could not print certificate path.");
261 }
262 else
263 {
264 Console.WriteLine("Certificate path:");
265 X509Certificate[] cert_path = trust_verification_result.GetCertPath();
266 for (int j = 0; j < cert_path.Length; j++)
267 {
268 Console.WriteLine("\tCertificate:");
269 X509Certificate full_cert = cert_path[j];
270 Console.WriteLine("\t\tIssuer names:");
271 X501AttributeTypeAndValue[] issuer_dn = full_cert.GetIssuerField().GetAllAttributesAndValues();
272 for (int i = 0; i < issuer_dn.Length; i++)
273 {
274 Console.WriteLine("\t\t\t" + issuer_dn[i].GetStringValue());
275 }
276 Console.WriteLine("\t\tSubject names:");
277 X501AttributeTypeAndValue[] subject_dn = full_cert.GetSubjectField().GetAllAttributesAndValues();
278 for (int i = 0; i < subject_dn.Length; i++)
279 {
280 Console.WriteLine("\t\t\t" + subject_dn[i].GetStringValue());
281 }
282 Console.WriteLine("\t\tExtensions:");
283 for (int i = 0; i < full_cert.GetExtensions().Length; i++)
284 {
285 Console.WriteLine("\t\t\t" + full_cert.GetExtensions()[i].ToString());
286 }
287 }
288 }
289 }
290 else
291 {
292 Console.WriteLine("No detailed trust verification result available.");
293 }
294
295 string[] unsupported_features = result.GetUnsupportedFeatures();
296 if (unsupported_features.Length > 0)
297 {
298 Console.WriteLine("Unsupported features:");
299
300 for (int i = 0; i < unsupported_features.Length; i++)
301 {
302 Console.WriteLine("\t" + unsupported_features[i]);
303 }
304 }
305 Console.WriteLine("==========");
306 }
307
308 return verification_status;
309 }
310 }
311
312 static void CertifyPDF(string in_docpath,
313 string in_cert_field_name,
314 string in_private_key_file_path,
315 string in_keyfile_password,
316 string in_appearance_image_path,
317 string in_outpath)
318 {
319 Console.Out.WriteLine("================================================================================");
320 Console.Out.WriteLine("Certifying PDF document");
321
322 // Open an existing PDF
323 using (PDFDoc doc = new PDFDoc(in_docpath))
324 {
325 Console.Out.WriteLine("PDFDoc has " + (doc.HasSignatures() ? "signatures" : "no signatures"));
326
327 Page page1 = doc.GetPage(1);
328
329 // Create a text field that we can lock using the field permissions feature.
330 TextWidget annot1 = TextWidget.Create(doc, new Rect(143, 440, 350, 460), "asdf_test_field");
331 page1.AnnotPushBack(annot1);
332
333 /* Create a new signature form field in the PDFDoc. The name argument is optional;
334 leaving it empty causes it to be auto-generated. However, you may need the name for later.
335 Acrobat doesn't show digsigfield in side panel if it's without a widget. Using a
336 Rect with 0 width and 0 height, or setting the NoPrint/Invisible flags makes it invisible. */
337 DigitalSignatureField certification_sig_field = doc.CreateDigitalSignatureField(in_cert_field_name);
338 SignatureWidget widgetAnnot = SignatureWidget.Create(doc, new Rect(143, 287, 219, 306), certification_sig_field);
339 page1.AnnotPushBack(widgetAnnot);
340
341 // (OPTIONAL) Add an appearance to the signature field.
342 Image img = Image.Create(doc, in_appearance_image_path);
343 widgetAnnot.CreateSignatureAppearance(img);
344
345 // Prepare the document locking permission level. It will be applied upon document certification.
346 Console.Out.WriteLine("Adding document permissions.");
347 certification_sig_field.SetDocumentPermissions(DigitalSignatureField.DocumentPermissions.e_annotating_formfilling_signing_allowed);
348
349 // Prepare to lock the text field that we created earlier.
350 Console.Out.WriteLine("Adding field permissions.");
351 string[] fields_to_lock = new string[1];
352 fields_to_lock[0] = "asdf_test_field";
353 certification_sig_field.SetFieldPermissions(DigitalSignatureField.FieldPermissions.e_include, fields_to_lock);
354
355 #if USE_DOTNET_CRYPTO
356 DotNetCryptoSignatureHandler sigHandler = new DotNetCryptoSignatureHandler(in_private_key_file_path, in_keyfile_password);
357 SignatureHandlerId sigHandlerId = doc.AddSignatureHandler(sigHandler);
358 certification_sig_field.CertifyOnNextSaveWithCustomHandler(sigHandlerId);
359 /* Add to the digital signature dictionary a SubFilter name that uniquely identifies the signature format
360 for verification tools. As an example, the custom handler defined in this file uses the CMS/PKCS #7 detached format,
361 so we embed one of the standard predefined SubFilter values: "adbe.pkcs7.detached". It is not necessary to do this
362 when using the StdSignatureHandler. */
363 Obj f_obj = certification_sig_field.GetSDFObj();
364 f_obj.FindObj("V").PutName("SubFilter", "adbe.pkcs7.detached");
365 #else
366 certification_sig_field.CertifyOnNextSave(in_private_key_file_path, in_keyfile_password);
367 #endif
368
369 // (OPTIONAL) Add more information to the signature dictionary.
370 certification_sig_field.SetLocation("Vancouver, BC");
371 certification_sig_field.SetReason("Document certification.");
372 certification_sig_field.SetContactInfo("www.pdftron.com");
373
374 // Save the PDFDoc. Once the method below is called, PDFNet will also sign the document using the information provided.
375 doc.Save(in_outpath, 0);
376 }
377
378 Console.Out.WriteLine("================================================================================");
379 }
380
381 static void SignPDF(string in_docpath,
382 string in_approval_field_name,
383 string in_private_key_file_path,
384 string in_keyfile_password,
385 string in_appearance_img_path,
386 string in_outpath)
387 {
388 Console.Out.WriteLine("================================================================================");
389 Console.Out.WriteLine("Signing PDF document");
390
391 // Open an existing PDF
392 using (PDFDoc doc = new PDFDoc(in_docpath))
393 {
394 // Retrieve the unsigned approval signature field.
395 Field found_approval_field = doc.GetField(in_approval_field_name);
396 DigitalSignatureField found_approval_signature_digsig_field = new DigitalSignatureField(found_approval_field);
397
398 // (OPTIONAL) Add an appearance to the signature field.
399 Image img = Image.Create(doc, in_appearance_img_path);
400 SignatureWidget found_approval_signature_widget = new SignatureWidget(found_approval_field.GetSDFObj());
401 found_approval_signature_widget.CreateSignatureAppearance(img);
402
403 // Prepare the signature and signature handler for signing.
404 #if USE_DOTNET_CRYPTO
405 DotNetCryptoSignatureHandler sigHandler = new DotNetCryptoSignatureHandler(in_private_key_file_path, in_keyfile_password);
406 SignatureHandlerId sigHandlerId = doc.AddSignatureHandler(sigHandler);
407 found_approval_signature_digsig_field.SignOnNextSaveWithCustomHandler(sigHandlerId);
408 /* Add a SubFilter name that uniquely identifies the signature format for verification tools. As an
409 example, the custom handler defined in this file uses the CMS/PKCS #7 detached format, so we embed
410 one of the standard predefined SubFilter values: "adbe.pkcs7.detached". It is not necessary to do this
411 when using the StdSignatureHandler.*/
412 Obj f_obj = found_approval_signature_digsig_field.GetSDFObj();
413 f_obj.FindObj("V").PutName("SubFilter", "adbe.pkcs7.detached");
414 #else
415 found_approval_signature_digsig_field.SignOnNextSave(in_private_key_file_path, in_keyfile_password);
416 #endif
417
418 // The actual approval signing will be done during the following incremental save operation.
419 doc.Save(in_outpath, SDFDoc.SaveOptions.e_incremental);
420 }
421 Console.Out.WriteLine("================================================================================");
422 }
423
424 static void ClearSignature(string in_docpath,
425 string in_digsig_field_name,
426 string in_outpath)
427 {
428 Console.Out.WriteLine("================================================================================");
429 Console.Out.WriteLine("Clearing certification signature");
430
431 using (PDFDoc doc = new PDFDoc(in_docpath))
432 {
433 DigitalSignatureField digsig = new DigitalSignatureField(doc.GetField(in_digsig_field_name));
434
435 Console.Out.WriteLine("Clearing signature: " + in_digsig_field_name);
436 digsig.ClearSignature();
437
438 if (!digsig.HasCryptographicSignature())
439 {
440 Console.Out.WriteLine("Cryptographic signature cleared properly.");
441 }
442
443 // Save incrementally so as to not invalidate other signatures from previous saves.
444 doc.Save(in_outpath, SDFDoc.SaveOptions.e_incremental);
445 }
446
447 Console.Out.WriteLine("================================================================================");
448 }
449
450 static void PrintSignaturesInfo(string in_docpath)
451 {
452 Console.Out.WriteLine("================================================================================");
453 Console.Out.WriteLine("Reading and printing digital signature information");
454
455 using (PDFDoc doc = new PDFDoc(in_docpath))
456 {
457 if (!doc.HasSignatures())
458 {
459 Console.Out.WriteLine("Doc has no signatures.");
460 Console.Out.WriteLine("================================================================================");
461 return;
462 }
463 else
464 {
465 Console.Out.WriteLine("Doc has signatures.");
466 }
467
468
469 for (FieldIterator fitr = doc.GetFieldIterator(); fitr.HasNext(); fitr.Next())
470 {
471 if (fitr.Current().IsLockedByDigitalSignature())
472 {
473 Console.Out.WriteLine("==========\nField locked by a digital signature");
474 }
475 else
476 {
477 Console.Out.WriteLine("==========\nField not locked by a digital signature");
478 }
479
480 Console.Out.WriteLine("Field name: " + fitr.Current().GetName());
481 Console.Out.WriteLine("==========");
482 }
483
484 Console.Out.WriteLine("====================\nNow iterating over digital signatures only.\n====================");
485
486 DigitalSignatureFieldIterator digsig_fitr = doc.GetDigitalSignatureFieldIterator();
487 for (; digsig_fitr.HasNext(); digsig_fitr.Next())
488 {
489 Console.Out.WriteLine("==========");
490 Console.Out.WriteLine("Field name of digital signature: " + new Field(digsig_fitr.Current().GetSDFObj()).GetName());
491
492 DigitalSignatureField digsigfield = digsig_fitr.Current();
493 if (!digsigfield.HasCryptographicSignature())
494 {
495 Console.Out.WriteLine("Either digital signature field lacks a digital signature dictionary, " +
496 "or digital signature dictionary lacks a cryptographic Contents entry. " +
497 "Digital signature field is not presently considered signed.\n" +
498 "==========");
499 continue;
500 }
501
502 int cert_count = digsigfield.GetCertCount();
503 Console.Out.WriteLine("Cert count: " + cert_count);
504 for (int i = 0; i < cert_count; ++i)
505 {
506 byte[] cert = digsigfield.GetCert(i);
507 Console.Out.WriteLine("Cert #" + i + " size: " + cert.Length);
508 }
509
510 DigitalSignatureField.SubFilterType subfilter = digsigfield.GetSubFilter();
511
512 Console.Out.WriteLine("Subfilter type: " + (int)subfilter);
513
514 if (subfilter != DigitalSignatureField.SubFilterType.e_ETSI_RFC3161)
515 {
516 Console.Out.WriteLine("Signature's signer: " + digsigfield.GetSignatureName());
517
518 Date signing_time = digsigfield.GetSigningTime();
519 if (signing_time.IsValid())
520 {
521 Console.Out.WriteLine("Signing time is valid.");
522 }
523
524 Console.Out.WriteLine("Location: " + digsigfield.GetLocation());
525 Console.Out.WriteLine("Reason: " + digsigfield.GetReason());
526 Console.Out.WriteLine("Contact info: " + digsigfield.GetContactInfo());
527 }
528 else
529 {
530 Console.Out.WriteLine("SubFilter == e_ETSI_RFC3161 (DocTimeStamp; no signing info)\n");
531 }
532
533 Console.Out.WriteLine(((digsigfield.HasVisibleAppearance()) ? "Visible" : "Not visible"));
534
535 DigitalSignatureField.DocumentPermissions digsig_doc_perms = digsigfield.GetDocumentPermissions();
536 string[] locked_fields = digsigfield.GetLockedFields();
537 foreach (string field_name in locked_fields)
538 {
539 Console.Out.WriteLine("This digital signature locks a field named: " + field_name);
540 }
541
542 switch (digsig_doc_perms)
543 {
544 case DigitalSignatureField.DocumentPermissions.e_no_changes_allowed:
545 Console.Out.WriteLine("No changes to the document can be made without invalidating this digital signature.");
546 break;
547 case DigitalSignatureField.DocumentPermissions.e_formfilling_signing_allowed:
548 Console.Out.WriteLine("Page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.");
549 break;
550 case DigitalSignatureField.DocumentPermissions.e_annotating_formfilling_signing_allowed:
551 Console.Out.WriteLine("Annotating, page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.");
552 break;
553 case DigitalSignatureField.DocumentPermissions.e_unrestricted:
554 Console.Out.WriteLine("Document not restricted by this digital signature.");
555 break;
556 default:
557 throw new Exception("Unrecognized digital signature document permission level.");
558 }
559 Console.Out.WriteLine("==========");
560 }
561 }
562
563 Console.Out.WriteLine("================================================================================");
564 }
565
566 static void CustomSigningAPI(string doc_path,
567 string cert_field_name,
568 string private_key_file_path,
569 string keyfile_password,
570 string public_key_file_path,
571 string appearance_image_path,
572 DigestAlgorithm.Type digest_algorithm_type,
573 bool PAdES_signing_mode,
574 string output_path)
575 {
576 Console.Out.WriteLine("================================================================================");
577 Console.Out.WriteLine("Custom signing PDF document");
578 using (PDFDoc doc = new PDFDoc(doc_path))
579 {
580 Page page1 = doc.GetPage(1);
581
582 DigitalSignatureField digsig_field = doc.CreateDigitalSignatureField(cert_field_name);
583 SignatureWidget widgetAnnot = SignatureWidget.Create(doc, new Rect(143, 287, 219, 306), digsig_field);
584 page1.AnnotPushBack(widgetAnnot);
585
586 // (OPTIONAL) Add an appearance to the signature field.
587 Image img = Image.Create(doc, appearance_image_path);
588 widgetAnnot.CreateSignatureAppearance(img);
589
590 // Create a digital signature dictionary inside the digital signature field, in preparation for signing.
591 digsig_field.CreateSigDictForCustomSigning("Adobe.PPKLite",
592 PAdES_signing_mode ? DigitalSignatureField.SubFilterType.e_ETSI_CAdES_detached : DigitalSignatureField.SubFilterType.e_adbe_pkcs7_detached,
593 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.
594 // ... or, if you want to apply a certification signature, use CreateSigDictForCustomCertification instead.
595
596 // (OPTIONAL) Set the signing time in the signature dictionary, if no secure embedded timestamping support is available from your signing provider.
597 Date current_date = new Date();
598 current_date.SetCurrentTime();
599 digsig_field.SetSigDictTimeOfSigning(current_date);
600
601 doc.Save(output_path, SDFDoc.SaveOptions.e_incremental);
602
603 // Digest the relevant bytes of the document in accordance with ByteRanges surrounding the signature.
604 byte[] pdf_digest = digsig_field.CalculateDigest(digest_algorithm_type);
605
606 X509Certificate signer_cert = new X509Certificate(public_key_file_path);
607
608 // Optionally, you can add a custom signed attribute at this point, such as one of the PAdES ESS attributes.
609 // The function we provide takes care of generating the correct PAdES ESS attribute depending on your digest algorithm.
610 byte[] pades_versioned_ess_signing_cert_attribute = DigitalSignatureField.GenerateESSSigningCertPAdESAttribute(signer_cert, digest_algorithm_type);
611
612 // Generate the signedAttrs component of CMS, passing any optional custom signedAttrs (e.g. PAdES ESS).
613 // The signedAttrs are certain attributes that become protected by their inclusion in the signature.
614 byte[] signedAttrs = DigitalSignatureField.GenerateCMSSignedAttributes(pdf_digest, pades_versioned_ess_signing_cert_attribute);
615
616 // Calculate the digest of the signedAttrs (i.e. not the PDF digest, this time).
617 byte[] signedAttrs_digest = DigestAlgorithm.CalculateDigest(digest_algorithm_type, signedAttrs);
618
619 //////////////////////////// custom digest signing starts ////////////////////////////
620 // At this point, you can sign the digest (for example, with HSM). We use our own SignDigest function instead here as an example,
621 // which you can also use for your purposes if necessary as an alternative to the handler/callback APIs (i.e. Certify/SignOnNextSave).
622 byte[] signature_value = DigestAlgorithm.SignDigest(
623 signedAttrs_digest,
624 digest_algorithm_type,
625 private_key_file_path,
626 keyfile_password);
627 //////////////////////////// custom digest signing ends //////////////////////////////
628
629 // Then, load all your chain certificates into a container of X509Certificate.
630 X509Certificate[] chain_certs = {};
631
632 // Then, create ObjectIdentifiers for the algorithms you have used.
633 // Here we use digest_algorithm_type (usually SHA256) for hashing, and RSAES-PKCS1-v1_5 (specified in the private key) for signing.
634 ObjectIdentifier digest_algorithm_oid = new ObjectIdentifier(digest_algorithm_type);
635 ObjectIdentifier signature_algorithm_oid = new ObjectIdentifier(ObjectIdentifier.Predefined.e_RSA_encryption_PKCS1);
636
637 // Then, put the CMS signature components together.
638 byte[] cms_signature = DigitalSignatureField.GenerateCMSSignature(
639 signer_cert, chain_certs, digest_algorithm_oid, signature_algorithm_oid,
640 signature_value, signedAttrs);
641
642 // Write the signature to the document.
643 doc.SaveCustomSignature(cms_signature, digsig_field, output_path);
644 }
645 Console.Out.WriteLine("================================================================================");
646 }
647
648 static bool TimestampAndEnableLTV(string doc_path,
649 string tsa_url,
650 string trusted_cert_path,
651 string appearance_img_path,
652 string output_path)
653 {
654 using (PDFDoc doc = new PDFDoc(doc_path))
655 {
656 DigitalSignatureField doctimestamp_signature_field = doc.CreateDigitalSignatureField();
657 TimestampingConfiguration tst_config = new TimestampingConfiguration(tsa_url);
658 VerificationOptions opts = new VerificationOptions(VerificationOptions.SignatureVerificationSecurityLevel.e_compatibility_and_archiving);
659 /* It is necessary to add to the VerificationOptions a trusted root certificate corresponding to
660 the chain used by the timestamp authority to sign the timestamp token, in order for the timestamp
661 response to be verifiable during DocTimeStamp signing. It is also necessary in the context of this
662 function to do this for the later LTV section, because one needs to be able to verify the DocTimeStamp
663 in order to enable LTV for it, and we re-use the VerificationOptions opts object in that part. */
664 opts.AddTrustedCertificate(trusted_cert_path);
665 /* By default, we only check online for revocation of certificates using the newer and lighter
666 OCSP protocol as opposed to CRL, due to lower resource usage and greater reliability. However,
667 it may be necessary to enable online CRL revocation checking in order to verify some timestamps
668 (i.e. those that do not have an OCSP responder URL for all non-trusted certificates). */
669 opts.EnableOnlineCRLRevocationChecking(true);
670
671 SignatureWidget widgetAnnot = SignatureWidget.Create(doc, new Rect(0, 100, 200, 150), doctimestamp_signature_field);
672 doc.GetPage(1).AnnotPushBack(widgetAnnot);
673 Obj widgetObj = widgetAnnot.GetSDFObj();
674
675 // (OPTIONAL) Add an appearance to the signature field.
676 Image img = Image.Create(doc, appearance_img_path);
677 widgetAnnot.CreateSignatureAppearance(img);
678
679 Console.WriteLine("Testing timestamping configuration.");
680 TimestampingResult config_result = tst_config.TestConfiguration(opts);
681 if (config_result.GetStatus())
682 {
683 Console.WriteLine("Success: timestamping configuration usable. Attempting to timestamp.");
684 }
685 else
686 {
687 // Print details of timestamping failure.
688 Console.WriteLine(config_result.GetString());
689 if (config_result.HasResponseVerificationResult())
690 {
691 EmbeddedTimestampVerificationResult tst_result = config_result.GetResponseVerificationResult();
692 Console.WriteLine("CMS digest status: {0}\n", tst_result.GetCMSDigestStatusAsString());
693 Console.WriteLine("Message digest status:{0}\n", tst_result.GetMessageImprintDigestStatusAsString());
694 Console.WriteLine("Trust status: {0}\n", tst_result.GetTrustStatusAsString());
695 }
696 return false;
697 }
698
699 doctimestamp_signature_field.TimestampOnNextSave(tst_config, opts);
700
701 // Save/signing throws if timestamping fails.
702 doc.Save(output_path, SDFDoc.SaveOptions.e_incremental);
703
704 Console.WriteLine("Timestamping successful. Adding LTV information for DocTimeStamp signature.");
705
706 // Add LTV information for timestamp signature to document.
707 VerificationResult timestamp_verification_result = doctimestamp_signature_field.Verify(opts);
708 if (!doctimestamp_signature_field.EnableLTVOfflineVerification(timestamp_verification_result))
709 {
710 Console.WriteLine("Could not enable LTV for DocTimeStamp.");
711 return false;
712 }
713 doc.Save(output_path, SDFDoc.SaveOptions.e_incremental);
714 Console.WriteLine("Added LTV information for DocTimeStamp signature successfully.");
715
716 return true;
717 }
718 }
719
720 private static pdftron.PDFNetLoader pdfNetLoader = pdftron.PDFNetLoader.Instance();
721 static Class1() {}
722
723 /// <summary>
724 /// The main entry point for the application.
725 /// </summary>
726 [STAThread]
727 static void Main(string[] args)
728 {
729 // Initialize PDFNetC
730 PDFNet.Initialize(PDFTronLicense.Key);
731
732 bool result = true;
733
734 //////////////////// TEST 0:
735 /* Create an approval signature field that we can sign after certifying.
736 (Must be done before calling CertifyOnNextSave/SignOnNextSave/WithCustomHandler.) */
737 try
738 {
739 using (PDFDoc doc = new PDFDoc(input_path + "waiver.pdf"))
740 {
741 DigitalSignatureField approval_signature_field = doc.CreateDigitalSignatureField("PDFTronApprovalSig");
742 SignatureWidget widgetAnnotApproval = SignatureWidget.Create(doc, new Rect(300, 287, 376, 306), approval_signature_field);
743 Page page1 = doc.GetPage(1);
744 page1.AnnotPushBack(widgetAnnotApproval);
745 doc.Save(output_path + "waiver_withApprovalField_output.pdf", SDFDoc.SaveOptions.e_remove_unused);
746 }
747 }
748 catch (Exception e)
749 {
750 Console.Error.WriteLine(e);
751 result = false;
752 }
753
754 //////////////////// TEST 1: certify a PDF.
755 try
756 {
757 CertifyPDF(input_path + "waiver_withApprovalField.pdf",
758 "PDFTronCertificationSig",
759 input_path + "pdftron.pfx",
760 "password",
761 input_path + "pdftron.bmp",
762 output_path + "waiver_withApprovalField_certified_output.pdf");
763 PrintSignaturesInfo(output_path + "waiver_withApprovalField_certified_output.pdf");
764 }
765 catch (Exception e)
766 {
767 Console.Error.WriteLine(e);
768 result = false;
769 }
770
771 //////////////////// TEST 2: approval-sign an existing, unsigned signature field in a PDF that already has a certified signature field.
772 try
773 {
774 SignPDF(input_path + "waiver_withApprovalField_certified.pdf",
775 "PDFTronApprovalSig",
776 input_path + "pdftron.pfx",
777 "password",
778 input_path + "signature.jpg",
779 output_path + "waiver_withApprovalField_certified_approved_output.pdf");
780 PrintSignaturesInfo(output_path + "waiver_withApprovalField_certified_approved_output.pdf");
781 }
782 catch (Exception e)
783 {
784 Console.Error.WriteLine(e);
785 result = false;
786 }
787
788 //////////////////// TEST 3: Clear a certification from a document that is certified and has an approval signature.
789 try
790 {
791 ClearSignature(input_path + "waiver_withApprovalField_certified_approved.pdf",
792 "PDFTronCertificationSig",
793 output_path + "waiver_withApprovalField_certified_approved_certcleared_output.pdf");
794 PrintSignaturesInfo(output_path + "waiver_withApprovalField_certified_approved_certcleared_output.pdf");
795 }
796 catch (Exception e)
797 {
798 Console.Error.WriteLine(e);
799 result = false;
800 }
801
802 //////////////////// TEST 4: Verify a document's digital signatures.
803 try
804 {
805 if (!VerifyAllAndPrint(input_path + "waiver_withApprovalField_certified_approved.pdf",
806 input_path + "pdftron.cer"))
807 {
808 result = false;
809 }
810 }
811 catch (Exception e)
812 {
813 Console.Error.WriteLine(e);
814 result = false;
815 }
816 //////////////////// TEST 5: Verify a document's digital signatures in a simple fashion using the document API.
817 try
818 {
819 if (!VerifySimple(input_path + "waiver_withApprovalField_certified_approved.pdf",
820 input_path + "pdftron.cer"))
821 {
822 result = false;
823 }
824 }
825 catch (Exception e)
826 {
827 Console.Error.WriteLine(e);
828 result = false;
829 }
830
831 //////////////////// TEST 6: Custom signing API.
832 // The Apryse custom signing API is a set of APIs related to cryptographic digital signatures
833 // which allows users to customize the process of signing documents. Among other things, this
834 // includes the capability to allow for easy integration of PDF-specific signing-related operations
835 // with access to Hardware Security Module (HSM) tokens/devices, access to cloud keystores, access
836 // to system keystores, etc.
837 try
838 {
839 CustomSigningAPI(input_path + "waiver.pdf",
840 "PDFTronApprovalSig",
841 input_path + "pdftron.pfx",
842 "password",
843 input_path + "pdftron.cer",
844 input_path + "signature.jpg",
845 DigestAlgorithm.Type.e_sha256,
846 true,
847 output_path + "waiver_custom_signed.pdf");
848 }
849 catch (Exception e)
850 {
851 Console.Error.WriteLine(e);
852 result = false;
853 }
854
855 //////////////////// TEST 7: Timestamp a document, then add Long Term Validation (LTV) information for the DocTimeStamp.
856 // try
857 // {
858 // // Replace YOUR_URL_OF_TSA with the timestamp authority (TSA) URL to use during timestamping.
859 // // For example, as of July 2024, http://timestamp.globalsign.com/tsa/r6advanced1 was usable.
860 // // Note that this url may not work in the future. A reliable solution requires using your own TSA.
861 // string tsa_url = "YOUR_URL_OF_TSA";
862 // if (tsa_url == "YOUR_URL_OF_TSA")
863 // {
864 // throw new Exception("Error: The URL of your timestamp authority was not specified.");
865 // }
866 //
867 // // Replace YOUR_CERTIFICATE with the trusted root certificate corresponding to the chain used by the timestamp authority.
868 // // For example, as of July 2024, https://secure.globalsign.com/cacert/gstsacasha384g4.crt was usable.
869 // // Note that this certificate may not work in the future. A reliable solution requires using your own TSA certificate.
870 // string trusted_cert_path = "YOUR_CERTIFICATE";
871 // if (trusted_cert_path == "YOUR_CERTIFICATE")
872 // {
873 // throw new Exception("Error: The path to your timestamp authority trusted root certificate was not specified.");
874 // }
875 //
876 // if (!TimestampAndEnableLTV(input_path + "waiver.pdf",
877 // tsa_url,
878 // trusted_cert_path,
879 // input_path + "signature.jpg",
880 // output_path+ "waiver_DocTimeStamp_LTV.pdf"))
881 // {
882 // result = false;
883 // }
884 // }
885 // catch (Exception e)
886 // {
887 // Console.Error.WriteLine(e);
888 // result = false;
889 // }
890
891 //////////////////// End of tests. ////////////////////
892 PDFNet.Terminate();
893 if (result)
894 {
895 Console.Out.WriteLine("Tests successful.\n==========");
896 }
897 else
898 {
899 Console.Out.WriteLine("Tests FAILED!!!\n==========");
900 }
901 }
902 }
903}

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales