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

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales