Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/source/tools/pdfsign.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 // Copyright (C) 2004-2021 Artifex Software, Inc. | |
| 2 // | |
| 3 // This file is part of MuPDF. | |
| 4 // | |
| 5 // MuPDF is free software: you can redistribute it and/or modify it under the | |
| 6 // terms of the GNU Affero General Public License as published by the Free | |
| 7 // Software Foundation, either version 3 of the License, or (at your option) | |
| 8 // any later version. | |
| 9 // | |
| 10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY | |
| 11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| 12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | |
| 13 // details. | |
| 14 // | |
| 15 // You should have received a copy of the GNU Affero General Public License | |
| 16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> | |
| 17 // | |
| 18 // Alternative licensing terms are available from the licensor. | |
| 19 // For commercial licensing, see <https://www.artifex.com/> or contact | |
| 20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, | |
| 21 // CA 94129, USA, for further information. | |
| 22 | |
| 23 /* | |
| 24 * PDF signature tool: verify and sign digital signatures in PDF files. | |
| 25 */ | |
| 26 | |
| 27 #include "mupdf/fitz.h" | |
| 28 #include "mupdf/pdf.h" | |
| 29 #include "mupdf/helpers/pkcs7-openssl.h" | |
| 30 | |
| 31 #include <string.h> | |
| 32 #include <stdlib.h> | |
| 33 #include <stdio.h> | |
| 34 | |
| 35 static char *infile = NULL; | |
| 36 static char *outfile = NULL; | |
| 37 static char *certificatefile = NULL; | |
| 38 static char *certificatepassword = ""; | |
| 39 static int verify = 0; | |
| 40 static int clear = 0; | |
| 41 static int sign = 0; | |
| 42 static int list = 1; | |
| 43 | |
| 44 static int usage(void) | |
| 45 { | |
| 46 fprintf(stderr, | |
| 47 "usage: mutool sign [options] input.pdf [signature object numbers]\n" | |
| 48 "\t-p -\tpassword\n" | |
| 49 "\t-v \tverify signature\n" | |
| 50 "\t-c \tclear signatures\n" | |
| 51 "\t-s -\tsign signatures using certificate file\n" | |
| 52 "\t-P -\tcertificate password\n" | |
| 53 "\t-o -\toutput file name\n" | |
| 54 ); | |
| 55 return 1; | |
| 56 } | |
| 57 | |
| 58 static void verify_signature(fz_context *ctx, pdf_document *doc, pdf_obj *signature) | |
| 59 { | |
| 60 char *name; | |
| 61 pdf_signature_error err; | |
| 62 pdf_pkcs7_verifier *verifier; | |
| 63 int edits; | |
| 64 pdf_pkcs7_distinguished_name *dn = NULL; | |
| 65 | |
| 66 printf("Verifying signature %d:\n", pdf_to_num(ctx, signature)); | |
| 67 | |
| 68 if (!pdf_signature_is_signed(ctx, doc, signature)) | |
| 69 { | |
| 70 printf("\tSignature is not signed.\n"); | |
| 71 return; | |
| 72 } | |
| 73 | |
| 74 verifier = pkcs7_openssl_new_verifier(ctx); | |
| 75 fz_var(dn); | |
| 76 fz_try(ctx) | |
| 77 { | |
| 78 dn = pdf_signature_get_signatory(ctx, verifier, doc, signature); | |
| 79 if (dn) | |
| 80 { | |
| 81 name = pdf_signature_format_distinguished_name(ctx, dn); | |
| 82 printf("\tDistinguished name: %s\n", name); | |
| 83 fz_free(ctx, name); | |
| 84 } | |
| 85 else | |
| 86 { | |
| 87 printf("\tSignature information missing.\n"); | |
| 88 } | |
| 89 | |
| 90 err = pdf_check_certificate(ctx, verifier, doc, signature); | |
| 91 if (err) | |
| 92 printf("\tCertificate error: %s\n", pdf_signature_error_description(err)); | |
| 93 else | |
| 94 printf("\tCertificate is trusted.\n"); | |
| 95 | |
| 96 err = pdf_check_digest(ctx, verifier, doc, signature); | |
| 97 edits = pdf_signature_incremental_change_since_signing(ctx, doc, signature); | |
| 98 if (err) | |
| 99 printf("\tDigest error: %s\n", pdf_signature_error_description(err)); | |
| 100 else if (edits) | |
| 101 printf("\tThe signature is valid but there have been edits since signing.\n"); | |
| 102 else | |
| 103 printf("\tThe document is unchanged since signing.\n"); | |
| 104 } | |
| 105 fz_always(ctx) | |
| 106 { | |
| 107 pdf_signature_drop_distinguished_name(ctx, dn); | |
| 108 pdf_drop_verifier(ctx, verifier); | |
| 109 } | |
| 110 fz_catch(ctx) | |
| 111 { | |
| 112 fz_rethrow(ctx); | |
| 113 } | |
| 114 } | |
| 115 | |
| 116 static void clear_signature(fz_context *ctx, pdf_document *doc, pdf_obj *signature) | |
| 117 { | |
| 118 pdf_page *page = NULL; | |
| 119 pdf_annot *widget; | |
| 120 pdf_obj *parent; | |
| 121 int pageno, pagenoend; | |
| 122 | |
| 123 fz_var(page); | |
| 124 | |
| 125 printf("Clearing signature %d.\n", pdf_to_num(ctx, signature)); | |
| 126 | |
| 127 fz_try(ctx) | |
| 128 { | |
| 129 parent = pdf_dict_get(ctx, signature, PDF_NAME(P)); | |
| 130 if (pdf_is_dict(ctx, parent)) | |
| 131 { | |
| 132 pageno = pdf_lookup_page_number(ctx, doc, parent); | |
| 133 pagenoend = pageno+1; | |
| 134 } | |
| 135 else | |
| 136 { | |
| 137 pageno = 0; | |
| 138 pagenoend = pdf_count_pages(ctx, doc); | |
| 139 } | |
| 140 for (; pageno < pagenoend; pageno++) | |
| 141 { | |
| 142 page = pdf_load_page(ctx, doc, pageno); | |
| 143 for (widget = pdf_first_widget(ctx, page); widget; widget = pdf_next_widget(ctx, widget)) | |
| 144 if (pdf_widget_type(ctx, widget) == PDF_WIDGET_TYPE_SIGNATURE && !pdf_objcmp_resolve(ctx, pdf_annot_obj(ctx, widget), signature)) | |
| 145 pdf_clear_signature(ctx, widget); | |
| 146 fz_drop_page(ctx, (fz_page *) page); | |
| 147 page = NULL; | |
| 148 } | |
| 149 } | |
| 150 fz_always(ctx) | |
| 151 fz_drop_page(ctx, (fz_page*)page); | |
| 152 fz_catch(ctx) | |
| 153 fz_rethrow(ctx); | |
| 154 } | |
| 155 | |
| 156 static void sign_signature(fz_context *ctx, pdf_document *doc, pdf_obj *signature) | |
| 157 { | |
| 158 pdf_pkcs7_signer *signer = NULL; | |
| 159 pdf_page *page = NULL; | |
| 160 pdf_annot *widget; | |
| 161 pdf_obj *parent; | |
| 162 int pageno, pagenoend; | |
| 163 | |
| 164 fz_var(page); | |
| 165 fz_var(signer); | |
| 166 | |
| 167 printf("Signing signature %d.\n", pdf_to_num(ctx, signature)); | |
| 168 | |
| 169 fz_try(ctx) | |
| 170 { | |
| 171 signer = pkcs7_openssl_read_pfx(ctx, certificatefile, certificatepassword); | |
| 172 | |
| 173 parent = pdf_dict_get(ctx, signature, PDF_NAME(P)); | |
| 174 if (pdf_is_dict(ctx, parent)) | |
| 175 { | |
| 176 pageno = pdf_lookup_page_number(ctx, doc, parent); | |
| 177 pagenoend = pageno+1; | |
| 178 } | |
| 179 else | |
| 180 { | |
| 181 pageno = 0; | |
| 182 pagenoend = pdf_count_pages(ctx, doc); | |
| 183 } | |
| 184 for (; pageno < pagenoend; pageno++) | |
| 185 { | |
| 186 page = pdf_load_page(ctx, doc, pageno); | |
| 187 for (widget = pdf_first_widget(ctx, page); widget; widget = pdf_next_widget(ctx, widget)) | |
| 188 if (pdf_widget_type(ctx, widget) == PDF_WIDGET_TYPE_SIGNATURE && !pdf_objcmp_resolve(ctx, pdf_annot_obj(ctx, widget), signature)) | |
| 189 pdf_sign_signature(ctx, widget, signer, | |
| 190 PDF_SIGNATURE_DEFAULT_APPEARANCE, | |
| 191 NULL, | |
| 192 NULL, | |
| 193 NULL); | |
| 194 fz_drop_page(ctx, (fz_page *) page); | |
| 195 page = NULL; | |
| 196 } | |
| 197 } | |
| 198 fz_always(ctx) | |
| 199 { | |
| 200 fz_drop_page(ctx, (fz_page*)page); | |
| 201 pdf_drop_signer(ctx, signer); | |
| 202 } | |
| 203 fz_catch(ctx) | |
| 204 fz_rethrow(ctx); | |
| 205 | |
| 206 } | |
| 207 | |
| 208 static void list_signature(fz_context *ctx, pdf_document *doc, pdf_obj *signature) | |
| 209 { | |
| 210 pdf_pkcs7_distinguished_name *dn; | |
| 211 pdf_pkcs7_verifier *verifier; | |
| 212 | |
| 213 if (!pdf_signature_is_signed(ctx, doc, signature)) | |
| 214 { | |
| 215 printf("%5d: Signature is not signed.\n", pdf_to_num(ctx, signature)); | |
| 216 return; | |
| 217 } | |
| 218 | |
| 219 verifier = pkcs7_openssl_new_verifier(ctx); | |
| 220 | |
| 221 dn = pdf_signature_get_signatory(ctx, verifier, doc, signature); | |
| 222 if (dn) | |
| 223 { | |
| 224 char *s = pdf_signature_format_distinguished_name(ctx, dn); | |
| 225 printf("%5d: Distinguished name: %s\n", pdf_to_num(ctx, signature), s); | |
| 226 fz_free(ctx, s); | |
| 227 pdf_signature_drop_distinguished_name(ctx, dn); | |
| 228 } | |
| 229 else | |
| 230 { | |
| 231 printf("%5d: Signature information missing.\n", pdf_to_num(ctx, signature)); | |
| 232 } | |
| 233 | |
| 234 pdf_drop_verifier(ctx, verifier); | |
| 235 | |
| 236 } | |
| 237 | |
| 238 static void process_field(fz_context *ctx, pdf_document *doc, pdf_obj *field) | |
| 239 { | |
| 240 if (pdf_dict_get_inheritable(ctx, field, PDF_NAME(FT)) != PDF_NAME(Sig)) | |
| 241 fz_warn(ctx, "%d is not a signature, skipping", pdf_to_num(ctx, field)); | |
| 242 else | |
| 243 { | |
| 244 if (list) | |
| 245 list_signature(ctx, doc, field); | |
| 246 if (verify) | |
| 247 verify_signature(ctx, doc, field); | |
| 248 if (clear) | |
| 249 clear_signature(ctx, doc, field); | |
| 250 if (sign) | |
| 251 sign_signature(ctx, doc, field); | |
| 252 } | |
| 253 } | |
| 254 | |
| 255 static int process_field_hierarchy(fz_context *ctx, pdf_document *doc, pdf_obj *field, pdf_cycle_list *cycle_up) | |
| 256 { | |
| 257 pdf_cycle_list cycle; | |
| 258 pdf_obj *kids; | |
| 259 int x = 0; | |
| 260 | |
| 261 if (field == NULL || pdf_cycle(ctx, &cycle, cycle_up, field)) | |
| 262 fz_throw(ctx, FZ_ERROR_SYNTAX, "recursive field hierarchy"); | |
| 263 | |
| 264 kids = pdf_dict_get(ctx, field, PDF_NAME(Kids)); | |
| 265 if (kids) | |
| 266 { | |
| 267 int i, n; | |
| 268 n = pdf_array_len(ctx, kids); | |
| 269 for (i = 0; i < n; ++i) | |
| 270 { | |
| 271 pdf_obj *kid = pdf_array_get(ctx, kids, i); | |
| 272 x += process_field_hierarchy(ctx, doc, kid, &cycle); | |
| 273 } | |
| 274 } | |
| 275 else if (pdf_dict_get_inheritable(ctx, field, PDF_NAME(FT)) == PDF_NAME(Sig)) | |
| 276 { | |
| 277 process_field(ctx, doc, field); | |
| 278 ++x; | |
| 279 } | |
| 280 | |
| 281 return x; | |
| 282 } | |
| 283 | |
| 284 static int process_acro_form(fz_context *ctx, pdf_document *doc) | |
| 285 { | |
| 286 pdf_obj *trailer = pdf_trailer(ctx, doc); | |
| 287 pdf_obj *root = pdf_dict_get(ctx, trailer, PDF_NAME(Root)); | |
| 288 pdf_obj *acroform = pdf_dict_get(ctx, root, PDF_NAME(AcroForm)); | |
| 289 pdf_obj *fields = pdf_dict_get(ctx, acroform, PDF_NAME(Fields)); | |
| 290 int i, x, n = pdf_array_len(ctx, fields); | |
| 291 for (i = x = 0; i < n; ++i) | |
| 292 x += process_field_hierarchy(ctx, doc, pdf_array_get(ctx, fields, i), NULL); | |
| 293 return x; | |
| 294 } | |
| 295 | |
| 296 int pdfsign_main(int argc, char **argv) | |
| 297 { | |
| 298 fz_context *ctx; | |
| 299 pdf_document *doc = NULL; | |
| 300 char *password = ""; | |
| 301 int c; | |
| 302 | |
| 303 while ((c = fz_getopt(argc, argv, "co:p:s:vP:")) != -1) | |
| 304 { | |
| 305 switch (c) | |
| 306 { | |
| 307 case 'c': list = 0; clear = 1; break; | |
| 308 case 'o': outfile = fz_optpath(fz_optarg); break; | |
| 309 case 'p': password = fz_optarg; break; | |
| 310 case 'P': certificatepassword = fz_optarg; break; | |
| 311 case 's': list = 0; sign = 1; certificatefile = fz_optarg; break; | |
| 312 case 'v': list = 0; verify = 1; break; | |
| 313 default: return usage(); | |
| 314 } | |
| 315 } | |
| 316 | |
| 317 if (argc - fz_optind < 1) | |
| 318 return usage(); | |
| 319 | |
| 320 infile = argv[fz_optind++]; | |
| 321 | |
| 322 if (!clear && !sign && !verify && argc - fz_optind > 0) | |
| 323 { | |
| 324 list = 0; | |
| 325 verify = 1; | |
| 326 } | |
| 327 | |
| 328 ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED); | |
| 329 if (!ctx) | |
| 330 { | |
| 331 fprintf(stderr, "cannot initialize context\n"); | |
| 332 exit(1); | |
| 333 } | |
| 334 | |
| 335 fz_var(doc); | |
| 336 | |
| 337 fz_try(ctx) | |
| 338 { | |
| 339 doc = pdf_open_document(ctx, infile); | |
| 340 if (pdf_needs_password(ctx, doc)) | |
| 341 if (!pdf_authenticate_password(ctx, doc, password)) | |
| 342 fz_warn(ctx, "cannot authenticate password: %s", infile); | |
| 343 | |
| 344 if (argc - fz_optind <= 0 || list) | |
| 345 { | |
| 346 if (!process_acro_form(ctx, doc)) | |
| 347 { | |
| 348 fprintf(stderr, "No signatures found!\n"); | |
| 349 exit(1); | |
| 350 } | |
| 351 } | |
| 352 else | |
| 353 { | |
| 354 while (argc - fz_optind) | |
| 355 { | |
| 356 pdf_obj *field = pdf_new_indirect(ctx, doc, fz_atoi(argv[fz_optind]), 0); | |
| 357 process_field(ctx, doc, field); | |
| 358 pdf_drop_obj(ctx, field); | |
| 359 fz_optind++; | |
| 360 } | |
| 361 } | |
| 362 | |
| 363 if (clear || sign) | |
| 364 { | |
| 365 pdf_write_options opts = pdf_default_write_options; | |
| 366 opts.do_incremental = 1; | |
| 367 if (!outfile) | |
| 368 outfile = "out.pdf"; | |
| 369 pdf_save_document(ctx, doc, outfile, &opts); | |
| 370 } | |
| 371 } | |
| 372 fz_always(ctx) | |
| 373 pdf_drop_document(ctx, doc); | |
| 374 fz_catch(ctx) | |
| 375 { | |
| 376 fz_report_error(ctx); | |
| 377 fz_log_error(ctx, "error processing signatures"); | |
| 378 } | |
| 379 | |
| 380 fz_flush_warnings(ctx); | |
| 381 fz_drop_context(ctx); | |
| 382 return 0; | |
| 383 } |
