DigitalSignatures

Sample Obj-C code to use Apryse SDK's high-level digital signature API for digitally signing and/or certifying PDF files. Learn more about our iOS 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, please specify OpenSSL include & lib paths to the Makefile
53//
54// If OpenSSL is installed elsewhere, it may be necessary to add the path to the headers in the $(INCLUDE) variable as
55// well as the location of either libcrypto.a or libcrypto.so/libcrypto.dylib.
56
57// Note for iOS development: This code can be used to digitally sign PDFs in iOS devices. When using the code signing
58// part of this code, it will be necessary to compile OpenSSL for iOS.
59
60#define USE_STD_SIGNATURE_HANDLER 1 // Comment out this line if you intend to use OpenSSLSignatureHandler rather than StdSignatureHandler.
61
62#import <CoreFoundation/CoreFoundation.h>
63
64#import <OBJC/PDFNetOBJC.h>
65//////////////////// Here follows an example of how to implement a custom signature handler. //////////
66#if (!USE_STD_SIGNATURE_HANDLER)
67
68// OpenSSL includes
69#include <openssl/err.h>
70#include <openssl/evp.h>
71#include <openssl/pkcs12.h>
72#include <openssl/pkcs7.h>
73#include <openssl/rsa.h>
74#include <openssl/sha.h>
75
76// Override PTSignatureHandler
77@interface OpenSSLSignatureHandler : PTSignatureHandler
78{
79 SHA256_CTX m_sha_ctx;
80 EVP_PKEY* mp_pkey; // private key
81 X509* mp_x509; // signing certificate
82 STACK_OF(X509)* mp_ca; // certificate chain up to the CA
83}
84- (NSString*) GetName;
85- (void) AppendData: (NSData*)data;
86- (BOOL) Reset;
87- (NSData*) CreateSignature;
88- (PTSignatureHandler*) Clone;
89- (id) init: (NSString*) pfxfile password: (NSString*) password;
90- (void) dealloc;
91@end // interface OpenSSLSignatureHandler
92
93@implementation OpenSSLSignatureHandler
94- (NSString*) GetName
95{
96 return (@"Adobe.PPKLite");
97}
98- (void) AppendData: (NSData*)data
99{
100 SHA256_Update(&m_sha_ctx, [data bytes], [data length]);
101 return;
102}
103- (BOOL) Reset
104{
105 SHA256_Init(&m_sha_ctx);
106 return (YES);
107}
108- (NSData*) CreateSignature
109{
110 unsigned char sha_buffer[SHA256_DIGEST_LENGTH];
111 memset((void*) sha_buffer, 0, SHA256_DIGEST_LENGTH);
112 SHA256_Final(sha_buffer, &m_sha_ctx);
113
114 PKCS7* p7 = PKCS7_new();
115 PKCS7_set_type(p7, NID_pkcs7_signed);
116
117 PKCS7_SIGNER_INFO* p7Si = PKCS7_add_signature(p7, mp_x509, mp_pkey, EVP_sha256());
118 PKCS7_add_attrib_content_type(p7Si, OBJ_nid2obj(NID_pkcs7_data));
119 PKCS7_add0_attrib_signing_time(p7Si, NULL);
120 PKCS7_add1_attrib_digest(p7Si, (const unsigned char*) sha_buffer, SHA256_DIGEST_LENGTH);
121 PKCS7_add_certificate(p7, mp_x509);
122
123 int c = 0;
124 for ( ; c < sk_X509_num(mp_ca); c++) {
125 X509* cert = sk_X509_value(mp_ca, c);
126 PKCS7_add_certificate(p7, cert);
127 }
128 PKCS7_set_detached(p7, 1);
129 PKCS7_content_new(p7, NID_pkcs7_data);
130
131 PKCS7_SIGNER_INFO_sign(p7Si);
132
133 int p7Len = i2d_PKCS7(p7, NULL);
134 NSMutableData* signature = [NSMutableData data];
135 unsigned char* p7Buf = (unsigned char*) malloc(p7Len);
136 if (p7Buf != NULL) {
137 unsigned char* pP7Buf = p7Buf;
138 i2d_PKCS7(p7, &pP7Buf);
139 [signature appendBytes: (const void*) p7Buf length: p7Len];
140 free(p7Buf);
141 }
142 PKCS7_free(p7);
143
144 return (signature);
145}
146- (PTSignatureHandler*) Clone
147{
148 return (self);
149}
150- (id) init: (NSString*) pfxfile password: (NSString*) password;
151{
152 self = [super init];
153
154 FILE* fp = fopen([pfxfile cStringUsingEncoding: NSASCIIStringEncoding], "rb");
155 if (fp == NULL)
156 @throw ([NSException exceptionWithName: @"PDFNet Exception" reason: @"Cannot open private key." userInfo: nil]);
157
158 PKCS12* p12 = d2i_PKCS12_fp(fp, NULL);
159 fclose(fp);
160
161 if (p12 == NULL)
162 @throw ([NSException exceptionWithName: @"PDFNet Exception" reason: @"Cannot parse private key." userInfo: nil]);
163
164 mp_pkey = NULL;
165 mp_x509 = NULL;
166 mp_ca = NULL;
167 int parseResult = PKCS12_parse(p12, [password cStringUsingEncoding: NSASCIIStringEncoding], &mp_pkey, &mp_x509, &mp_ca);
168 PKCS12_free(p12);
169
170 if (parseResult == 0)
171 @throw ([NSException exceptionWithName: @"PDFNet Exception" reason: @"Cannot parse private key." userInfo: nil]);
172
173 [self Reset];
174
175 return (self);
176}
177- (void) dealloc
178{
179 sk_X509_free(mp_ca);
180 X509_free(mp_x509);
181 EVP_PKEY_free(mp_pkey);
182}
183@end // implementation OpenSSLSignatureHandler
184
185#endif // (!USE_STD_SIGNATURE_HANDLER)
186////////// End of the OpenSSLSignatureHandler custom handler code. ////////////////////
187
188BOOL VerifySimple(NSString* in_docpath, NSString* in_public_key_file_path)
189{
190 PTPDFDoc* doc = [[PTPDFDoc alloc] initWithFilepath:in_docpath];
191 NSLog(@"==========");
192 PTVerificationOptions* opts = [[PTVerificationOptions alloc] initWithLevel:e_ptcompatibility_and_archiving];
193
194 // Add trust root to store of trusted certificates contained in VerificationOptions.
195 [opts AddTrustedCertificateWithFilePath: in_public_key_file_path in_trust_flags:(e_ptdefault_trust | e_ptcertification_trust)];
196
197 PTSignaturesVerificationStatus result = [doc VerifySignedDigitalSignatures: opts];
198 switch (result)
199 {
200 case e_ptdoc_unsigned:
201 NSLog(@"Document has no signed signature fields.");
202 return NO;
203 /* e_failure == bad doc status, digest status, or permissions status
204 (i.e. does not include trust issues, because those are flaky due to being network/config-related) */
205 case e_ptdoc_failure:
206 NSLog(@"Hard failure in verification on at least one signature.");
207 return NO;
208 case e_ptdoc_untrusted:
209 NSLog(@"Could not verify trust for at least one signature.");
210 return NO;
211 case e_ptdoc_unsupported:
212 /* If necessary, call GetUnsupportedFeatures on VerificationResult to check which
213 unsupported features were encountered (requires verification using 'detailed' APIs) */
214 NSLog(@"At least one signature contains unsupported features.");
215 return NO;
216 // unsigned sigs skipped; parts of document may be unsigned (check GetByteRanges on signed sigs to find out)
217 case e_ptdoc_verified:
218 NSLog(@"All signed signatures in document verified.");
219 return YES;
220 default:
221 [NSException raise:@"unrecognized document verification status" format:@"unrecognized document verification status"];
222 }
223}
224
225BOOL VerifyAllAndPrint(NSString* in_docpath, NSString* in_public_key_file_path)
226{
227 PTPDFDoc* doc = [[PTPDFDoc alloc] initWithFilepath:in_docpath];
228 NSLog(@"==========");
229 PTVerificationOptions* opts = [[PTVerificationOptions alloc] initWithLevel:e_ptcompatibility_and_archiving];
230
231 // Add trust root to store of trusted certificates contained in VerificationOptions.
232 [opts AddTrustedCertificateWithFilePath: in_public_key_file_path in_trust_flags:(e_ptdefault_trust | e_ptcertification_trust)];
233
234 // Iterate over the signatures and verify all of them.
235 PTDigitalSignatureFieldIterator* digsig_fitr = [doc GetDigitalSignatureFieldIterator];
236 BOOL verification_status = YES;
237 for (; [digsig_fitr HasNext]; [digsig_fitr Next])
238 {
239 PTDigitalSignatureField* curr = [digsig_fitr Current];
240 PTVerificationResult* result = [curr Verify: opts];
241 if ([result GetVerificationStatus])
242 {
243 NSLog(@"Signature verified, objnum: %u", [[curr GetSDFObj] GetObjNum]);
244 }
245 else
246 {
247 NSLog(@"Signature verification failed, objnum: %u", [[curr GetSDFObj] GetObjNum]);
248 verification_status = NO;
249 }
250
251 switch ([result GetDigestAlgorithm])
252 {
253 case e_ptsha1:
254 NSLog(@"Digest algorithm: SHA-1");
255 break;
256 case e_ptsha256:
257 NSLog(@"Digest algorithm: SHA-256");
258 break;
259 case e_ptsha384:
260 NSLog(@"Digest algorithm: SHA-384");
261 break;
262 case e_ptsha512:
263 NSLog(@"Digest algorithm: SHA-512");
264 break;
265 case e_ptripemd160:
266 NSLog(@"Digest algorithm: RIPEMD-160");
267 break;
268 case e_ptunknown_digest_algorithm:
269 NSLog(@"Digest algorithm: unknown");
270 break;
271 default:
272 [NSException raise:@"unrecognized digest algorithm" format:@"unrecognized digest algorithm"];
273 }
274
275 NSLog(@"Detailed verification result: \n\t%@\n\t%@\n\t%@\n\t%@",
276 [result GetDocumentStatusAsString],
277 [result GetDigestStatusAsString],
278 [result GetTrustStatusAsString],
279 [result GetPermissionsStatusAsString]);
280
281
282 NSArray<PTDisallowedChange *>* changes = [result GetDisallowedChanges];
283 for (id change in changes)
284 {
285 NSLog(@"\tDisallowed change: %@, objnum: %u", [change GetTypeAsString], [change GetObjNum]);
286 }
287
288 // Get and print all the detailed trust-related results, if they are available.
289 if ([result HasTrustVerificationResult])
290 {
291 PTTrustVerificationResult* trust_verification_result = [result GetTrustVerificationResult];
292 NSLog([trust_verification_result WasSuccessful]? @"Trust verified." : @"Trust not verifiable.");
293 NSLog(@"%@", [trust_verification_result GetResultString]);
294
295 unsigned long tmp_time_t = [trust_verification_result GetTimeOfTrustVerification];
296 switch ([trust_verification_result GetTimeOfTrustVerificationEnum])
297 {
298 case e_ptcurrent:
299 NSLog(@"Trust verification attempted with respect to current time (as epoch time): %lu", tmp_time_t);
300 break;
301 case e_ptsigning:
302 NSLog(@"Trust verification attempted with respect to signing time (as epoch time): %lu", tmp_time_t);
303 break;
304 case e_pttimestamp:
305 NSLog(@"Trust verification attempted with respect to secure embedded timestamp (as epoch time): %lu", tmp_time_t);
306 break;
307 default:
308 [NSException raise:@"unrecognized time enum value" format:@"unrecognized time enum value"];
309 }
310
311 if ([[trust_verification_result GetCertPath ] count] == 0)
312 {
313 NSLog(@"Could not print certificate path.\n");
314 }
315 else
316 {
317 NSLog(@"Certificate path:\n");
318 NSArray<PTX509Certificate*> *cert_path = [trust_verification_result GetCertPath];
319 for (int j = 0; j < [cert_path count]; j++)
320
321 {
322 NSLog(@"\tCertificate:\n");
323 PTX509Certificate* full_cert = cert_path[j];
324 NSLog(@"\t\tIssuer names:\n");
325 NSArray<PTX501AttributeTypeAndValue*>*issuer_dn = [[full_cert GetIssuerField] GetAllAttributesAndValues];
326 for (int i = 0; i < [issuer_dn count]; i++)
327 {
328 NSLog(@"\t\t\t%@",[[issuer_dn objectAtIndex:i] GetStringValue]);
329 }
330 NSLog(@"\t\tSubject names:\n");
331 NSArray<PTX501AttributeTypeAndValue*>* subject_dn = [[full_cert GetSubjectField] GetAllAttributesAndValues];
332 for (int i = 0; i < [subject_dn count]; i++)
333 {
334 NSLog(@"\t\t\t%@\n", [[subject_dn objectAtIndex:i] GetStringValue]);
335 }
336 NSLog(@"\t\tExtensions:\n");
337 NSArray<PTX509Extension*> *ex = [full_cert GetExtensions];
338 for (size_t i = 0; i < [ex count]; i++)
339 {
340 NSLog(@"\t\t\t%@\n",[[ex objectAtIndex:i] ToString]);
341 }
342 }
343 }
344 }
345 else
346 {
347 NSLog(@"No detailed trust verification result available.");
348 }
349
350 NSArray<NSString *> *unsupported_features = [ result GetUnsupportedFeatures ];
351 for (int i = 0; i < [unsupported_features count]; i++)
352 {
353 NSLog(@"%@", [unsupported_features objectAtIndex:i]);
354 }
355 NSLog(@"==========");
356 }
357
358 return verification_status;
359}
360
361void CertifyPDF(NSString* in_docpath,
362 NSString* in_cert_field_name,
363 NSString* in_private_key_file_path,
364 NSString* in_keyfile_password,
365 NSString* in_appearance_image_path,
366 NSString* in_outpath)
367{
368 NSLog(@"================================================================================");
369 NSLog(@"Certifying PDF document");
370
371 // Open an existing PDF
372 PTPDFDoc* doc = [[PTPDFDoc alloc] initWithFilepath: in_docpath];
373
374 NSLog(@"PDFDoc has %@", ([doc HasSignatures] ? @"signatures" : @"no signatures"));
375
376 PTPage* page1 = [doc GetPage: 1];
377
378 // Create a text field that we can lock using the field permissions feature.
379 PTTextWidget* annot1 = [PTTextWidget Create: doc pos: [[PTPDFRect alloc] initWithX1: 143 y1: 440 x2: 350 y2: 460] field_name: @"asdf_test_field"];
380 [page1 AnnotPushBack: annot1];
381
382 /* Create a new signature form field in the PDFDoc. The name argument is optional;
383 leaving it empty causes it to be auto-generated. However, you may need the name for later.
384 Acrobat doesn't show digsigfield in side panel if it's without a widget. Using a
385 Rect with 0 width and 0 height, or setting the NoPrint/Invisible flags makes it invisible. */
386 PTDigitalSignatureField* certification_sig_field = [doc CreateDigitalSignatureField: in_cert_field_name];
387 PTSignatureWidget* widgetAnnot = [PTSignatureWidget CreateWithDigitalSignatureField: doc pos: [[PTPDFRect alloc] initWithX1: 143 y1: 287 x2: 219 y2: 306] field: certification_sig_field];
388 [page1 AnnotPushBack: widgetAnnot];
389
390 // (OPTIONAL) Add an appearance to the signature field.
391 PTImage* img = [PTImage CreateWithFile: [doc GetSDFDoc] filename: in_appearance_image_path encoder_hints: [[PTObj alloc]init]];
392 [widgetAnnot CreateSignatureAppearance: img];
393
394 // Prepare the document locking permission level. It will be applied upon document certification.
395 NSLog(@"Adding document permissions.");
396 [certification_sig_field SetDocumentPermissions: e_ptannotating_formfilling_signing_allowed];
397
398 // Prepare to lock the text field that we created earlier.
399 NSLog(@"Adding field permissions.");
400 NSMutableArray<NSString *> * fields_to_lock = [[NSMutableArray<NSString *> alloc] init];
401 [fields_to_lock addObject:@"asdf_test_field"];
402 [certification_sig_field SetFieldPermissions: e_ptdigsig_permission_include in_field_names: [fields_to_lock copy]];
403
404#ifdef USE_STD_SIGNATURE_HANDLER
405 [certification_sig_field CertifyOnNextSave: in_private_key_file_path in_password: in_keyfile_password];
406#else
407 OpenSSLSignatureHandler* sigHandler = [[OpenSSLSignatureHandler alloc] init:in_private_key_file_path password: in_keyfile_password];
408 PTSignatureHandlerId sigHandlerId = [doc AddSignatureHandler: sigHandler];
409 [certification_sig_field CertifyOnNextSaveWithCustomHandler: sigHandlerId];
410 /* Add to the digital signature dictionary a SubFilter name that uniquely identifies the signature format
411 for verification tools. As an example, the custom handler defined in this file uses the CMS/PKCS #7 detached format,
412 so we embed one of the standard predefined SubFilter values: "adbe.pkcs7.detached". It is not necessary to do this
413 when using the StdSignatureHandler. */
414 PTObj* f_obj = [certification_sig_field GetSDFObj];
415 [[f_obj FindObj: @"V"] PutName: @"SubFilter" name: @"adbe.pkcs7.detached"];
416#endif
417
418 // (OPTIONAL) Add more information to the signature dictionary.
419 [certification_sig_field SetLocation: @"Vancouver, BC"];
420 [certification_sig_field SetReason: @"Document certification."];
421 [certification_sig_field SetContactInfo: @"www.pdftron.com"];
422
423 // Save the PDFDoc. Once the method below is called, PDFNet will also sign the document using the information provided.
424 [doc SaveToFile: in_outpath flags: 0];
425
426 NSLog(@"================================================================================");
427}
428
429void SignPDF(NSString* in_docpath,
430 NSString* in_approval_field_name,
431 NSString* in_private_key_file_path,
432 NSString* in_keyfile_password,
433 NSString* in_appearance_img_path,
434 NSString* in_outpath)
435{
436 NSLog(@"================================================================================");
437 NSLog(@"Signing PDF document");
438
439 // Open an existing PDF
440 PTPDFDoc* doc = [[PTPDFDoc alloc] initWithFilepath: in_docpath];
441
442 // Retrieve the unsigned approval signature field.
443 PTField* found_approval_field = [doc GetField: in_approval_field_name];
444 PTDigitalSignatureField* found_approval_signature_digsig_field = [[PTDigitalSignatureField alloc] initWithIn_field: found_approval_field];
445
446 // (OPTIONAL) Add an appearance to the signature field.
447 PTImage* img = [PTImage CreateWithFile: [doc GetSDFDoc] filename: in_appearance_img_path encoder_hints: [[PTObj alloc]init]];
448 PTSignatureWidget* found_approval_signature_widget = [[PTSignatureWidget alloc] initWithD: [found_approval_field GetSDFObj]];
449 [found_approval_signature_widget CreateSignatureAppearance: img];
450
451 // Prepare the signature and signature handler for signing.
452#ifdef USE_STD_SIGNATURE_HANDLER
453 [found_approval_signature_digsig_field SignOnNextSave: in_private_key_file_path in_password: in_keyfile_password];
454#else
455 OpenSSLSignatureHandler* sigHandler = [[OpenSSLSignatureHandler alloc] init:in_private_key_file_path password: in_keyfile_password];
456 PTSignatureHandlerId sigHandlerId = [doc AddSignatureHandler: sigHandler];
457 [found_approval_signature_digsig_field SignOnNextSaveWithCustomHandler: sigHandlerId];
458 /* Add to the digital signature dictionary a SubFilter name that uniquely identifies the signature format
459 for verification tools. As an example, the custom handler defined in this file uses the CMS/PKCS #7 detached format,
460 so we embed one of the standard predefined SubFilter values: "adbe.pkcs7.detached". It is not necessary to do this
461 when using the StdSignatureHandler. */
462 PTObj* f_obj = [found_approval_signature_digsig_field GetSDFObj];
463 [[f_obj FindObj: @"V"] PutName: @"SubFilter" name: @"adbe.pkcs7.detached"];
464#endif
465
466 // The actual approval signing will be done during the following incremental save operation.
467 [doc SaveToFile: in_outpath flags: e_ptincremental];
468
469 NSLog(@"================================================================================");
470}
471
472void ClearSignature(NSString* in_docpath,
473 NSString* in_digsig_field_name,
474 NSString* in_outpath)
475{
476 NSLog(@"================================================================================");
477 NSLog(@"Clearing certification signature");
478
479 PTPDFDoc* doc = [[PTPDFDoc alloc] initWithFilepath: in_docpath];
480
481 PTDigitalSignatureField* digsig = [[PTDigitalSignatureField alloc] initWithIn_field: [doc GetField: in_digsig_field_name]];
482
483 NSLog(@"Clearing signature: %@", in_digsig_field_name);
484 [digsig ClearSignature];
485
486 if (![digsig HasCryptographicSignature])
487 {
488 NSLog(@"Cryptographic signature cleared properly.");
489 }
490
491 // Save incrementally so as to not invalidate other signatures from previous saves.
492 [doc SaveToFile: in_outpath flags: e_ptincremental];
493
494 NSLog(@"================================================================================");
495}
496
497void PrintSignaturesInfo(NSString* in_docpath)
498{
499 NSLog(@"================================================================================");
500 NSLog(@"Reading and printing digital signature information");
501
502 PTPDFDoc* doc = [[PTPDFDoc alloc] initWithFilepath: in_docpath];
503 if (![doc HasSignatures])
504 {
505 NSLog(@"Doc has no signatures.");
506 NSLog(@"================================================================================");
507 return;
508 }
509 else
510 {
511 NSLog(@"Doc has signatures.");
512 }
513
514
515 PTFieldIterator* fitr = [doc GetFieldIterator];
516 for(; [fitr HasNext]; [fitr Next])
517 {
518 NSLog(@"==========");
519 if ([[fitr Current] IsLockedByDigitalSignature])
520 {
521 NSLog(@"Field locked by a digital signature");
522 }
523 else
524 {
525 NSLog(@"Field not locked by a digital signature");
526 }
527
528 NSLog(@"Field name: %@", [[fitr Current] GetName]);
529 NSLog(@"==========");
530 }
531
532 NSLog(@"====================");
533 NSLog(@"Now iterating over digital signatures only.");
534 NSLog(@"====================");
535
536 PTDigitalSignatureFieldIterator* digsig_fitr = [doc GetDigitalSignatureFieldIterator];
537 for (; [digsig_fitr HasNext]; [digsig_fitr Next])
538 {
539 NSLog(@"==========");
540 NSLog(@"Field name of digital signature: %@", [[[PTField alloc] initWithField_dict: [[digsig_fitr Current] GetSDFObj]] GetName]);
541
542 PTDigitalSignatureField* digsigfield = [digsig_fitr Current];
543 if (![digsigfield HasCryptographicSignature])
544 {
545 NSLog(@"Either digital signature field lacks a digital signature dictionary, "
546 @"or digital signature dictionary lacks a cryptographic Contents entry. "
547 @"Digital signature field is not presently considered signed.");
548 NSLog(@"==========");
549 continue;
550 }
551
552 int cert_count = [digsigfield GetCertCount];
553 NSLog(@"Cert count: %d", cert_count);
554 for (int i = 0; i < cert_count; ++i)
555 {
556 NSData* cert = [digsigfield GetCert:i];
557 NSLog(@"Cert #%d size: %lu", i, cert.length);
558 }
559
560 PTDigitalSignatureFieldSubFilterType subfilter = [digsigfield GetSubFilter];
561
562 NSLog(@"Subfilter type: %d", (int)subfilter);
563
564 if (subfilter != e_ptETSI_RFC3161)
565 {
566 NSLog(@"Signature's signer: %@", [digsigfield GetSignatureName] == NULL? @"" : [digsigfield GetSignatureName]);
567
568 PTDate* signing_time = [digsigfield GetSigningTime];
569 if ([signing_time IsValid])
570 {
571 NSLog(@"Signing time is valid.");
572 }
573
574 NSLog(@"Location: %@", [digsigfield GetLocation] == NULL? @"" : [digsigfield GetLocation]);
575 NSLog(@"Reason: %@", [digsigfield GetReason] == NULL? @"" : [digsigfield GetReason]);
576 NSLog(@"Contact info: %@", [digsigfield GetContactInfo] == NULL? @"" : [digsigfield GetContactInfo]);
577 }
578 else
579 {
580 NSLog(@"SubFilter == e_ETSI_RFC3161 (DocTimeStamp; no signing info)");
581 }
582
583 NSLog(([digsigfield HasVisibleAppearance]) ? @"Visible" : @"Not visible");
584
585 PTDigitalSignatureFieldDocumentPermissions digsig_doc_perms = [digsigfield GetDocumentPermissions];
586 NSArray<NSString *>* locked_fields = [digsigfield GetLockedFields];
587 for (id field_name in locked_fields)
588 {
589 NSLog(@"This digital signature locks a field named: %@", field_name);
590 }
591
592 switch (digsig_doc_perms)
593 {
594 case e_ptno_changes_allowed:
595 NSLog(@"No changes to the document can be made without invalidating this digital signature.");
596 break;
597 case e_ptformfilling_signing_allowed:
598 NSLog(@"Page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.");
599 break;
600 case e_ptannotating_formfilling_signing_allowed:
601 NSLog(@"Annotating, page template instantiation, form filling, and signing digital signatures are allowed without invalidating this digital signature.");
602 break;
603 case e_ptunrestricted:
604 NSLog(@"Document not restricted by this digital signature.");
605 break;
606 default:
607 [NSException raise:@"Unrecognized digital signature document permission level." format:@"Unrecognized digital signature document permission level."];
608 }
609 NSLog(@"==========");
610 }
611
612 NSLog(@"================================================================================");
613}
614
615void CustomSigningAPI(NSString* doc_path,
616 NSString* cert_field_name,
617 NSString* private_key_file_path,
618 NSString* keyfile_password,
619 NSString* public_key_file_path,
620 NSString* appearance_image_path,
621 PTDigestAlgorithmType digest_algorithm_type,
622 BOOL PAdES_signing_mode,
623 NSString* output_path)
624{
625 NSLog(@"================================================================================");
626 NSLog(@"Custom signing PDF document");
627
628 PTPDFDoc* doc = [[PTPDFDoc alloc] initWithFilepath: doc_path];
629 PTPage* page1 = [doc GetPage: 1];
630
631 PTDigitalSignatureField* digsig_field = [doc CreateDigitalSignatureField: cert_field_name];
632 PTSignatureWidget* widgetAnnot = [PTSignatureWidget CreateWithDigitalSignatureField: doc pos: [[PTPDFRect alloc] initWithX1: 143 y1: 287 x2: 219 y2: 306] field: digsig_field];
633 [page1 AnnotPushBack: widgetAnnot];
634
635 // (OPTIONAL) Add an appearance to the signature field.
636 PTImage* img = [PTImage Create: [doc GetSDFDoc] filename: appearance_image_path];
637 [widgetAnnot CreateSignatureAppearance: img];
638
639 // Create a digital signature dictionary inside the digital signature field, in preparation for signing.
640 [digsig_field CreateSigDictForCustomSigning: @"Adobe.PPKLite"
641 in_subfilter_type: PAdES_signing_mode ? e_ptETSI_CAdES_detached : e_ptadbe_pkcs7_detached
642 in_contents_size_to_reserve: 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.
643 // ... or, if you want to apply a certification signature, use CreateSigDictForCustomCertification instead.
644
645 // (OPTIONAL) Set the signing time in the signature dictionary, if no secure embedded timestamping support is available from your signing provider.
646 PTDate* current_date = [[PTDate alloc] init];
647 [current_date SetCurrentTime];
648 [digsig_field SetSigDictTimeOfSigning: current_date];
649
650 [doc SaveToFile: output_path flags: e_ptincremental];
651
652 // Digest the relevant bytes of the document in accordance with ByteRanges surrounding the signature.
653 NSData* pdf_digest = [digsig_field CalculateDigest: digest_algorithm_type];
654
655 PTX509Certificate* signer_cert = [[PTX509Certificate alloc] initWithIn_certificate_path: public_key_file_path];
656
657 // Optionally, you can add a custom signed attribute at this point, such as one of the PAdES ESS attributes.
658 // The function we provide takes care of generating the correct PAdES ESS attribute depending on your digest algorithm.
659 NSData* pades_versioned_ess_signing_cert_attribute = [PTDigitalSignatureField GenerateESSSigningCertPAdESAttribute: signer_cert in_digest_algorithm_type: digest_algorithm_type];
660
661 // Generate the signedAttrs component of CMS, passing any optional custom signedAttrs (e.g. PAdES ESS).
662 // The signedAttrs are certain attributes that become protected by their inclusion in the signature.
663 NSData* signedAttrs = [PTDigitalSignatureField GenerateCMSSignedAttributes: pdf_digest in_custom_signedattributes_buf: pades_versioned_ess_signing_cert_attribute];
664
665 // Calculate the digest of the signedAttrs (i.e. not the PDF digest, this time).
666 NSData* signedAttrs_digest = [PTDigestAlgorithm CalculateDigest: digest_algorithm_type in_message_buf: signedAttrs];
667
668 //////////////////////////// custom digest signing starts ////////////////////////////
669 // At this point, you can sign the digest (for example, with HSM). We use our own SignDigest function instead here as an example,
670 // which you can also use for your purposes if necessary as an alternative to the handler/callback APIs (i.e. Certify/SignOnNextSave).
671 NSData* signature_value = [PTDigestAlgorithm SignDigest: signedAttrs_digest
672 digest_algorithm_type: digest_algorithm_type
673 pkcs12_keyfile_path: private_key_file_path
674 pkcs12_password: keyfile_password];
675 //////////////////////////// custom digest signing ends //////////////////////////////
676
677 // Then, load all your chain certificates into a container of X509Certificate.
678 NSArray<PTX509Certificate *> *chain_certs;
679
680 // Then, create ObjectIdentifiers for the algorithms you have used.
681 // Here we use digest_algorithm_type (usually SHA256) for hashing, and RSAES-PKCS1-v1_5 (specified in the private key) for signing.
682 PTObjectIdentifier* digest_algorithm_oid = [[PTObjectIdentifier alloc] initWithIn_digest_algorithm_type: digest_algorithm_type];
683 PTObjectIdentifier* signature_algorithm_oid = [[PTObjectIdentifier alloc] initWithIn_oid_enum: e_ptRSA_encryption_PKCS1];
684
685 // Then, put the CMS signature components together.
686 NSData* cms_signature = [PTDigitalSignatureField GenerateCMSSignature: signer_cert in_chain_certs_list: chain_certs
687 in_digest_algorithm_oid: digest_algorithm_oid in_signature_algorithm_oid: signature_algorithm_oid
688 in_signature_value_buf: signature_value in_signedattributes_buf: signedAttrs];
689
690 // Write the signature to the document.
691 [doc SaveCustomSignatureToFile: cms_signature in_field: digsig_field in_path: output_path];
692
693 NSLog(@"================================================================================");
694}
695
696BOOL TimestampAndEnableLTV(NSString* doc_path,
697 NSString* tsa_url,
698 NSString* trusted_cert_path,
699 NSString* appearance_img_path,
700 NSString* output_path)
701{
702 PTPDFDoc* doc = [[PTPDFDoc alloc] initWithFilepath: doc_path];
703 PTDigitalSignatureField* doctimestamp_signature_field = [doc CreateDigitalSignatureField: @""];
704 PTTimestampingConfiguration* tst_config = [[PTTimestampingConfiguration alloc] initWithIn_url: tsa_url];
705 PTVerificationOptions* opts = [[PTVerificationOptions alloc] initWithLevel:e_ptcompatibility_and_archiving];
706
707 /* It is necessary to add to the VerificationOptions a trusted root certificate corresponding to
708 the chain used by the timestamp authority to sign the timestamp token, in order for the timestamp
709 response to be verifiable during DocTimeStamp signing. It is also necessary in the context of this
710 function to do this for the later LTV section, because one needs to be able to verify the DocTimeStamp
711 in order to enable LTV for it, and we re-use the VerificationOptions opts object in that part. */
712 [ opts AddTrustedCertificateWithFilePath: trusted_cert_path in_trust_flags:e_ptdefault_trust];
713 /* By default, we only check online for revocation of certificates using the newer and lighter
714 OCSP protocol as opposed to CRL, due to lower resource usage and greater reliability. However,
715 it may be necessary to enable online CRL revocation checking in order to verify some timestamps
716 (i.e. those that do not have an OCSP responder URL for all non-trusted certificates). */
717 [ opts EnableOnlineCRLRevocationChecking: YES];
718
719 PTSignatureWidget* widgetAnnot = [PTSignatureWidget CreateWithDigitalSignatureField: doc pos: [[PTPDFRect alloc] initWithX1: 0 y1: 100 x2: 200 y2: 150] field: doctimestamp_signature_field];
720 [[doc GetPage: 1] AnnotPushBack: widgetAnnot];
721
722 // (OPTIONAL) Add an appearance to the signature field.
723 PTImage* img = [PTImage CreateWithFile: [doc GetSDFDoc] filename: appearance_img_path encoder_hints: [[PTObj alloc]init]];
724
725 [widgetAnnot CreateSignatureAppearance: img];
726
727 NSLog(@"Testing timestamping configuration.");
728 PTTimestampingResult* config_result = [tst_config TestConfiguration:opts];
729 if([ config_result GetStatus])
730 {
731 NSLog(@"Success: timestamping configuration usable. Attempting to timestamp.");
732 }
733 else
734 {
735 // Print details of timestamping failure.
736 NSLog(@"%@", [config_result GetString]);
737 if ([config_result HasResponseVerificationResult])
738 {
739 PTEmbeddedTimestampVerificationResult* tst_result = [config_result GetResponseVerificationResult];
740 NSLog(@"CMS digest status: %@\n", [tst_result GetCMSDigestStatusAsString]);
741 NSLog(@"Message digest status: %@\n", [tst_result GetMessageImprintDigestStatusAsString]);
742 NSLog(@"Trust status: %@\n", [tst_result GetTrustStatusAsString]);
743 }
744 return NO;
745 }
746
747 [doctimestamp_signature_field TimestampOnNextSave: tst_config in_timestamp_response_verification_options: opts];
748
749 // Save/signing throws if timestamping fails.
750 [doc SaveToFile: output_path flags: e_ptincremental];
751 NSLog(@"Timestamping successful. Adding LTV information for DocTimeStamp signature.");
752
753 // Add LTV information for timestamp signature to document.
754 PTVerificationResult* timestamp_verification_result = [doctimestamp_signature_field Verify: opts];
755 if (! [doctimestamp_signature_field EnableLTVOfflineVerification: timestamp_verification_result])
756 {
757 NSLog(@"Could not enable LTV for DocTimeStamp.");
758 return NO;
759 }
760 [doc SaveToFile: output_path flags: e_ptincremental];
761 NSLog(@"Added LTV information for DocTimeStamp signature successfully.");
762
763 return YES;
764}
765
766int main(int argc, char *argv[])
767{
768 @autoreleasepool {
769
770 // Initialize PDFNetC
771 [PTPDFNet Initialize: 0];
772
773
774 #if (!USE_STD_SIGNATURE_HANDLER)
775 // Initialize OpenSSL library
776 CRYPTO_malloc_init();
777 ERR_load_crypto_strings();
778 OpenSSL_add_all_algorithms();
779 #endif // (!USE_STD_SIGNATURE_HANDLER)
780
781 int ret = 0;
782
783 NSString* input_path = @"../../TestFiles/";
784 NSString* output_path = @"../../TestFiles/Output/";
785
786 //////////////////// TEST 0:
787 /* Create an approval signature field that we can sign after certifying.
788 (Must be done before calling CertifyOnNextSave/SignOnNextSave/WithCustomHandler.) */
789 @try
790 {
791 PTPDFDoc* doc = [[PTPDFDoc alloc] initWithFilepath:[NSString stringWithFormat:@"%@/%@", input_path, @"waiver.pdf"]];
792 PTDigitalSignatureField* approval_signature_field = [doc CreateDigitalSignatureField: @"PDFTronApprovalSig"];
793 PTSignatureWidget* widgetAnnotApproval = [PTSignatureWidget CreateWithDigitalSignatureField: doc pos: [[PTPDFRect alloc] initWithX1: 300 y1: 287 x2: 376 y2: 306] field: approval_signature_field];
794 PTPage* page1 = [doc GetPage: 1];
795 [page1 AnnotPushBack: widgetAnnotApproval];
796 [doc SaveToFile: [NSString stringWithFormat:@"%@/%@", output_path, @"waiver_withApprovalField_output.pdf"] flags: e_ptremove_unused];
797 }
798 @catch (NSException* e)
799 {
800 NSLog(@"Exception: %@ - %@\n", e.name, e.reason);
801 ret = 1;
802 }
803
804 //////////////////// TEST 1: certify a PDF.
805 @try
806 {
807 CertifyPDF([NSString stringWithFormat:@"%@/%@", input_path, @"waiver_withApprovalField.pdf"],
808 @"PDFTronCertificationSig",
809 [NSString stringWithFormat:@"%@/%@", input_path, @"pdftron.pfx"],
810 @"password",
811 [NSString stringWithFormat:@"%@/%@", input_path, @"pdftron.bmp"],
812 [NSString stringWithFormat:@"%@/%@", output_path, @"waiver_withApprovalField_certified_output.pdf"]);
813 PrintSignaturesInfo([NSString stringWithFormat:@"%@/%@", output_path, @"waiver_withApprovalField_certified_output.pdf"]);
814 }
815 @catch (NSException* e)
816 {
817 NSLog(@"Exception: %@ - %@\n", e.name, e.reason);
818 ret = 1;
819 }
820
821 //////////////////// TEST 2: approval-sign an existing, unsigned signature field in a PDF that already has a certified signature field.
822 @try
823 {
824 SignPDF([NSString stringWithFormat:@"%@/%@", input_path, @"waiver_withApprovalField_certified.pdf"],
825 @"PDFTronApprovalSig",
826 [NSString stringWithFormat:@"%@/%@", input_path, @"pdftron.pfx"],
827 @"password",
828 [NSString stringWithFormat:@"%@/%@", input_path, @"signature.jpg"],
829 [NSString stringWithFormat:@"%@/%@", output_path, @"waiver_withApprovalField_certified_approved_output.pdf"]);
830 PrintSignaturesInfo([NSString stringWithFormat:@"%@/%@", output_path, @"waiver_withApprovalField_certified_approved_output.pdf"]);
831 }
832 @catch (NSException* e)
833 {
834 NSLog(@"Exception: %@ - %@\n", e.name, e.reason);
835 ret = 1;
836 }
837
838 //////////////////// TEST 3: Clear a certification from a document that is certified and has an approval signature.
839 @try
840 {
841 ClearSignature([NSString stringWithFormat:@"%@/%@", input_path, @"waiver_withApprovalField_certified_approved.pdf"],
842 @"PDFTronCertificationSig",
843 [NSString stringWithFormat:@"%@/%@", output_path, @"waiver_withApprovalField_certified_approved_certcleared_output.pdf"]);
844 PrintSignaturesInfo([NSString stringWithFormat:@"%@/%@", output_path, @"waiver_withApprovalField_certified_approved_certcleared_output.pdf"]);
845 }
846 @catch (NSException* e)
847 {
848 NSLog(@"Exception: %@ - %@\n", e.name, e.reason);
849 ret = 1;
850 }
851
852 //////////////////// TEST 4: Verify a document's digital signatures.
853 @try
854 {
855 if (!VerifyAllAndPrint([NSString stringWithFormat:@"%@/%@", input_path, @"waiver_withApprovalField_certified_approved.pdf"],
856 [NSString stringWithFormat:@"%@/%@", input_path, @"pdftron.cer"]))
857 {
858 ret = 1;
859 }
860 }
861 @catch (NSException* e)
862 {
863 NSLog(@"Exception: %@ - %@\n", e.name, e.reason);
864 ret = 1;
865 }
866
867 //////////////////// TEST 5: Verify a document's digital signatures in a simple fashion using the document API.
868 @try
869 {
870 if (!VerifySimple([NSString stringWithFormat:@"%@/%@", input_path, @"waiver_withApprovalField_certified_approved.pdf"],
871 [NSString stringWithFormat:@"%@/%@", input_path, @"pdftron.cer"]))
872 {
873 ret = 1;
874 }
875 }
876 @catch (NSException* e)
877 {
878 NSLog(@"Exception: %@ - %@\n", e.name, e.reason);
879 ret = 1;
880 }
881
882 //////////////////// TEST 6: Custom signing API.
883 // The Apryse custom signing API is a set of APIs related to cryptographic digital signatures
884 // which allows users to customize the process of signing documents. Among other things, this
885 // includes the capability to allow for easy integration of PDF-specific signing-related operations
886 // with access to Hardware Security Module (HSM) tokens/devices, access to cloud keystores, access
887 // to system keystores, etc.
888 @try
889 {
890 CustomSigningAPI([NSString stringWithFormat:@"%@/%@", input_path, @"waiver.pdf"],
891 @"PDFTronApprovalSig",
892 [NSString stringWithFormat:@"%@/%@", input_path, @"pdftron.pfx"],
893 @"password",
894 [NSString stringWithFormat:@"%@/%@", input_path, @"pdftron.cer"],
895 [NSString stringWithFormat:@"%@/%@", input_path, @"signature.jpg"],
896 e_ptsha256,
897 YES,
898 [NSString stringWithFormat:@"%@/%@", output_path, @"waiver_custom_signed.pdf"]);
899 }
900 @catch (NSException* e)
901 {
902 NSLog(@"Exception: %@ - %@\n", e.name, e.reason);
903 ret = 1;
904 }
905
906 //////////////////// TEST 7: Timestamp a document, then add Long Term Validation (LTV) information for the DocTimeStamp.
907 // @try
908 // {
909 // // Replace YOUR_URL_OF_TSA with the timestamp authority (TSA) URL to use during timestamping.
910 // // For example, as of July 2024, http://timestamp.globalsign.com/tsa/r6advanced1 was usable.
911 // // Note that this url may not work in the future. A reliable solution requires using your own TSA.
912 // NSString* tsa_url = @"YOUR_URL_OF_TSA";
913 // if ([tsa_url isEqualToString:@"YOUR_URL_OF_TSA"])
914 // {
915 // [NSException raise:@"Error: The URL of your timestamp authority was not specified." format:@"Error: The URL of your timestamp authority was not specified."];
916 // }
917 //
918 // // Replace YOUR_CERTIFICATE with the trusted root certificate corresponding to the chain used by the timestamp authority.
919 // // For example, as of July 2024, https://secure.globalsign.com/cacert/gstsacasha384g4.crt was usable.
920 // // Note that this certificate may not work in the future. A reliable solution requires using your own TSA certificate.
921 // NSString* trusted_cert_path = @"YOUR_CERTIFICATE";
922 // if ([trusted_cert_path isEqualToString:@"YOUR_CERTIFICATE"])
923 // {
924 // [NSException raise:@"Error: The path to your timestamp authority trusted root certificate was not specified." format:@"Error: The path to your timestamp authority trusted root certificate was not specified."];
925 // }
926 //
927 // if (!TimestampAndEnableLTV([NSString stringWithFormat:@"%@/%@", input_path, @"waiver.pdf"],
928 // tsa_url,
929 // trusted_cert_path,
930 // [NSString stringWithFormat:@"%@/%@", input_path, @"signature.jpg"],
931 // [NSString stringWithFormat:@"%@/%@", output_path, @"waiver_DocTimeStamp_LTV.pdf"]))
932 // {
933 // ret = 1;
934 // }
935 // }
936 // @catch (NSException* e)
937 // {
938 // NSLog(@"Exception: %@ - %@\n", e.name, e.reason);
939 // ret = 1;
940 // }
941
942 //////////////////// End of tests. ////////////////////
943
944 if (!ret)
945 {
946 NSLog(@"Tests successful.");
947 NSLog(@"==========");
948 }
949 else
950 {
951 NSLog(@"Tests FAILED!!!");
952 NSLog(@"==========");
953 }
954
955 #if (!USE_STD_SIGNATURE_HANDLER)
956 ERR_free_strings();
957 EVP_cleanup();
958 #endif // (!USE_STD_SIGNATURE_HANDLER)
959 [PTPDFNet Terminate: 0];
960 return ret;
961 }
962}

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales