Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/pdf/pdf-signature.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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/pdf/pdf-signature.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,668 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" +#include "pdf-annot-imp.h" + +#include <string.h> +#include <time.h> + +enum +{ + PDF_SIGFLAGS_SIGSEXIST = 1, + PDF_SIGFLAGS_APPENDONLY = 2 +}; + +static void +begin_widget_op(fz_context *ctx, pdf_annot *annot, const char *op) +{ + if (!annot->page) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation not bound to any page"); + + pdf_begin_operation(ctx, annot->page->doc, op); +} + +static void +end_widget_op(fz_context *ctx, pdf_annot *annot) +{ + pdf_end_operation(ctx, annot->page->doc); +} + +static void +abandon_widget_op(fz_context *ctx, pdf_annot *annot) +{ + pdf_abandon_operation(ctx, annot->page->doc); +} + +void pdf_write_digest(fz_context *ctx, fz_output *out, pdf_obj *byte_range, pdf_obj *field, size_t hexdigest_offset, size_t hexdigest_length, pdf_pkcs7_signer *signer) +{ + fz_stream *stm = NULL; + fz_stream *in = NULL; + fz_range *brange = NULL; + int brange_len = pdf_array_len(ctx, byte_range)/2; + unsigned char *digest = NULL; + size_t digest_len; + pdf_obj *v = pdf_dict_get(ctx, field, PDF_NAME(V)); + size_t len; + char *cstr = NULL; + + fz_var(stm); + fz_var(in); + fz_var(brange); + fz_var(digest); + fz_var(cstr); + + if (hexdigest_length < 4) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Bad parameters to pdf_write_digest"); + + len = (hexdigest_length - 2) / 2; + + fz_try(ctx) + { + int i; + size_t z; + + brange = fz_calloc(ctx, brange_len, sizeof(*brange)); + for (i = 0; i < brange_len; i++) + { + brange[i].offset = pdf_array_get_int(ctx, byte_range, 2*i); + brange[i].length = pdf_array_get_int(ctx, byte_range, 2*i+1); + } + + stm = fz_stream_from_output(ctx, out); + in = fz_open_range_filter(ctx, stm, brange, brange_len); + + digest = fz_malloc(ctx, len); + digest_len = signer->create_digest(ctx, signer, in, digest, len); + if (digest_len == 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "signer provided no signature digest"); + if (digest_len > len) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "signature digest larger than space for digest"); + + fz_drop_stream(ctx, in); + in = NULL; + fz_drop_stream(ctx, stm); + stm = NULL; + + fz_seek_output(ctx, out, (int64_t)hexdigest_offset+1, SEEK_SET); + cstr = fz_malloc(ctx, len); + + for (z = 0; z < len; z++) + { + int val = z < digest_len ? digest[z] : 0; + fz_write_printf(ctx, out, "%02x", val); + cstr[z] = val; + } + + pdf_dict_put_string(ctx, v, PDF_NAME(Contents), cstr, len); + } + fz_always(ctx) + { + fz_free(ctx, cstr); + fz_free(ctx, digest); + fz_free(ctx, brange); + fz_drop_stream(ctx, stm); + fz_drop_stream(ctx, in); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +typedef struct fieldname_prefix +{ + struct fieldname_prefix *prev; + char name[FZ_FLEXIBLE_ARRAY]; +} fieldname_prefix; + +typedef struct +{ + pdf_locked_fields *locked; + fieldname_prefix *prefix; +} sig_locking_data; + +static void +check_field_locking(fz_context *ctx, pdf_obj *obj, void *data_, pdf_obj **ff) +{ + fieldname_prefix *prefix = NULL; + sig_locking_data *data = (sig_locking_data *)data_; + + fz_var(prefix); + + fz_try(ctx) + { + const char *name = NULL; + size_t n = 1; + pdf_obj *t; + + t = pdf_dict_get(ctx, obj, PDF_NAME(T)); + if (t != NULL) + { + name = pdf_to_text_string(ctx, t); + n += strlen(name); + } + if (data->prefix->name[0] && name) + n += 1; + if (data->prefix->name[0]) + n += strlen(data->prefix->name); + prefix = fz_malloc_flexible(ctx, fieldname_prefix, name, n); + prefix->prev = data->prefix; + if (data->prefix->name[0]) + strcpy(prefix->name, data->prefix->name); + if (data->prefix->name[0] && name) + strcat(prefix->name, "."); + if (name) + strcat(prefix->name, name); + data->prefix = prefix; + + if (pdf_name_eq(ctx, pdf_dict_get(ctx, obj, PDF_NAME(Type)), PDF_NAME(Annot)) && + pdf_name_eq(ctx, pdf_dict_get(ctx, obj, PDF_NAME(Subtype)), PDF_NAME(Widget))) + { + int flags = pdf_to_int(ctx, *ff); + + if (((flags & PDF_FIELD_IS_READ_ONLY) == 0) && /* Field is not currently locked */ + pdf_is_field_locked(ctx, data->locked, data->prefix->name)) /* Field should be locked */ + pdf_dict_put_int(ctx, obj, PDF_NAME(Ff), flags | PDF_FIELD_IS_READ_ONLY); + } + } + fz_catch(ctx) + { + if (prefix) + { + data->prefix = prefix->prev; + fz_free(ctx, prefix); + } + fz_rethrow(ctx); + } +} + +static void +pop_field_locking(fz_context *ctx, pdf_obj *obj, void *data_) +{ + fieldname_prefix *prefix; + sig_locking_data *data = (sig_locking_data *)data_; + + prefix = data->prefix; + data->prefix = data->prefix->prev; + fz_free(ctx, prefix); +} + +static void enact_sig_locking(fz_context *ctx, pdf_document *doc, pdf_obj *sig) +{ + pdf_locked_fields *locked = pdf_find_locked_fields_for_sig(ctx, doc, sig); + pdf_obj *fields; + static pdf_obj *ff_names[2] = { PDF_NAME(Ff), NULL }; + pdf_obj *ff = NULL; + static fieldname_prefix null_prefix = { NULL, { 0 } }; + sig_locking_data data = { locked, &null_prefix }; + + if (locked == NULL) + return; + + fz_try(ctx) + { + fields = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm/Fields"); + pdf_walk_tree(ctx, fields, PDF_NAME(Kids), check_field_locking, pop_field_locking, &data, &ff_names[0], &ff); + } + fz_always(ctx) + pdf_drop_locked_fields(ctx, locked); + fz_catch(ctx) + { + pop_field_locking(ctx, NULL, &data); + fz_rethrow(ctx); + } +} + +void +pdf_sign_signature_with_appearance(fz_context *ctx, pdf_annot *widget, pdf_pkcs7_signer *signer, int64_t t, fz_display_list *disp_list) +{ + pdf_document *doc; + + if (pdf_dict_get_inheritable(ctx, widget->obj, PDF_NAME(FT)) != PDF_NAME(Sig)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation is not a signature widget"); + if (pdf_widget_is_readonly(ctx, widget)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Signature is read only, it cannot be signed."); + + begin_widget_op(ctx, widget, "Sign signature"); + doc = widget->page->doc; + + fz_try(ctx) + { + pdf_obj *wobj = ((pdf_annot *)widget)->obj; + pdf_obj *form; + int sf; + + pdf_dirty_annot(ctx, widget); + + /* Ensure that all fields that will be locked by this signature + * are marked as ReadOnly. */ + enact_sig_locking(ctx, doc, wobj); + + if (disp_list) + pdf_set_annot_appearance_from_display_list(ctx, widget, "N", NULL, fz_identity, disp_list); + + /* Update the SigFlags for the document if required */ + form = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm"); + if (!form) + { + pdf_obj *root = pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root)); + form = pdf_dict_put_dict(ctx, root, PDF_NAME(AcroForm), 1); + } + + sf = pdf_to_int(ctx, pdf_dict_get(ctx, form, PDF_NAME(SigFlags))); + if ((sf & (PDF_SIGFLAGS_SIGSEXIST | PDF_SIGFLAGS_APPENDONLY)) != (PDF_SIGFLAGS_SIGSEXIST | PDF_SIGFLAGS_APPENDONLY)) + pdf_dict_put_int(ctx, form, PDF_NAME(SigFlags), sf | PDF_SIGFLAGS_SIGSEXIST | PDF_SIGFLAGS_APPENDONLY); + + pdf_signature_set_value(ctx, doc, wobj, signer, t); + end_widget_op(ctx, widget); + } + fz_catch(ctx) + { + abandon_widget_op(ctx, widget); + fz_rethrow(ctx); + } +} + +static pdf_pkcs7_distinguished_name placeholder_dn = { + "Your Common Name Here", + "Organization", + "Organizational Unit", + "Email", + "Country" +}; + +static char * +pdf_format_signature_info(fz_context *ctx, pdf_pkcs7_signer *signer, int flags, const char *reason, const char *location, int64_t now, char **name) +{ + pdf_pkcs7_distinguished_name *dn = NULL; + char *info; + fz_var(dn); + fz_try(ctx) + { + if (signer) + dn = signer->get_signing_name(ctx, signer); + if (!dn) + dn = &placeholder_dn; + *name = fz_strdup(ctx, dn->cn ? dn->cn : "Your Common Name Here"); + info = pdf_signature_info(ctx, + (flags & PDF_SIGNATURE_SHOW_TEXT_NAME) ? *name : NULL, + (flags & PDF_SIGNATURE_SHOW_DN) ? dn : NULL, + reason, + location, + (flags & PDF_SIGNATURE_SHOW_DATE) ? now : -1, + (flags & PDF_SIGNATURE_SHOW_LABELS) ? 1 : 0); + } + fz_always(ctx) + { + if (dn != &placeholder_dn) + pdf_signature_drop_distinguished_name(ctx, dn); + } + fz_catch(ctx) + fz_rethrow(ctx); + return info; +} + + +void pdf_sign_signature(fz_context *ctx, pdf_annot *widget, + pdf_pkcs7_signer *signer, + int flags, + fz_image *graphic, + const char *reason, + const char *location) +{ + int logo = flags & PDF_SIGNATURE_SHOW_LOGO; + fz_rect rect = pdf_annot_rect(ctx, widget); + fz_text_language lang = pdf_annot_language(ctx, widget); +#ifdef CLUSTER + int64_t now = 1112281971; /* release date of MuPDF 0.1 */ +#else + int64_t now = time(NULL); +#endif + char *name = NULL; + char *info = NULL; + fz_display_list *dlist = NULL; + + fz_var(dlist); + fz_var(info); + fz_var(name); + + /* Create an appearance stream only if the signature is intended to be visible */ + fz_try(ctx) + { + if (!fz_is_empty_rect(rect)) + { + info = pdf_format_signature_info(ctx, signer, flags, reason, location, now, &name); + if (graphic) + dlist = pdf_signature_appearance_signed(ctx, rect, lang, graphic, NULL, info, logo); + else if (flags & PDF_SIGNATURE_SHOW_GRAPHIC_NAME) + dlist = pdf_signature_appearance_signed(ctx, rect, lang, NULL, name, info, logo); + else + dlist = pdf_signature_appearance_signed(ctx, rect, lang, NULL, NULL, info, logo); + } + pdf_sign_signature_with_appearance(ctx, widget, signer, now, dlist); + } + fz_always(ctx) + { + fz_free(ctx, info); + fz_free(ctx, name); + fz_drop_display_list(ctx, dlist); + } + fz_catch(ctx) + fz_rethrow(ctx); +} + +fz_display_list *pdf_preview_signature_as_display_list(fz_context *ctx, + float w, float h, fz_text_language lang, + pdf_pkcs7_signer *signer, + int flags, + fz_image *graphic, + const char *reason, + const char *location) +{ + int logo = flags & PDF_SIGNATURE_SHOW_LOGO; + fz_rect rect = fz_make_rect(0, 0, w, h); + int64_t now = time(NULL); + char *name = NULL; + char *info = NULL; + fz_display_list *dlist = NULL; + + fz_var(dlist); + fz_var(info); + fz_var(name); + + fz_try(ctx) + { + info = pdf_format_signature_info(ctx, signer, flags, reason, location, now, &name); + if (graphic) + dlist = pdf_signature_appearance_signed(ctx, rect, lang, graphic, NULL, info, logo); + else if (flags & PDF_SIGNATURE_SHOW_GRAPHIC_NAME) + dlist = pdf_signature_appearance_signed(ctx, rect, lang, NULL, name, info, logo); + else + dlist = pdf_signature_appearance_signed(ctx, rect, lang, NULL, NULL, info, logo); + } + fz_always(ctx) + { + fz_free(ctx, info); + fz_free(ctx, name); + } + fz_catch(ctx) + fz_rethrow(ctx); + + return dlist; +} + +fz_pixmap *pdf_preview_signature_as_pixmap(fz_context *ctx, + int w, int h, fz_text_language lang, + pdf_pkcs7_signer *signer, + int flags, + fz_image *graphic, + const char *reason, + const char *location) +{ + fz_pixmap *pix; + fz_display_list *dlist = pdf_preview_signature_as_display_list(ctx, + w, h, lang, + signer, flags, graphic, reason, location); + fz_try(ctx) + pix = fz_new_pixmap_from_display_list(ctx, dlist, fz_identity, fz_device_rgb(ctx), 0); + fz_always(ctx) + fz_drop_display_list(ctx, dlist); + fz_catch(ctx) + fz_rethrow(ctx); + return pix; +} + +void pdf_clear_signature(fz_context *ctx, pdf_annot *widget) +{ + int flags; + fz_display_list *dlist = NULL; + + if (pdf_dict_get_inheritable(ctx, widget->obj, PDF_NAME(FT)) != PDF_NAME(Sig)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation is not a signature widget"); + if (pdf_widget_is_readonly(ctx, widget)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "read only signature cannot be cleared"); + + begin_widget_op(ctx, widget, "Clear Signature"); + + fz_var(dlist); + fz_try(ctx) + { + fz_text_language lang = pdf_annot_language(ctx, (pdf_annot *)widget); + fz_rect rect = pdf_annot_rect(ctx, widget); + + pdf_begin_operation(ctx, widget->page->doc, "Clear Signature"); + if (pdf_widget_is_readonly(ctx, widget)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Signature read only, it cannot be cleared."); + + pdf_xref_remove_unsaved_signature(ctx, ((pdf_annot *)widget)->page->doc, ((pdf_annot *)widget)->obj); + + pdf_dirty_annot(ctx, widget); + + flags = pdf_dict_get_int(ctx, ((pdf_annot *)widget)->obj, PDF_NAME(F)); + flags &= ~PDF_ANNOT_IS_LOCKED; + if (flags) + pdf_dict_put_int(ctx, ((pdf_annot *)widget)->obj, PDF_NAME(F), flags); + else + pdf_dict_del(ctx, ((pdf_annot *)widget)->obj, PDF_NAME(F)); + + pdf_dict_del(ctx, ((pdf_annot *)widget)->obj, PDF_NAME(V)); + + dlist = pdf_signature_appearance_unsigned(ctx, rect, lang); + pdf_set_annot_appearance_from_display_list(ctx, widget, "N", NULL, fz_identity, dlist); + end_widget_op(ctx, widget); + } + fz_always(ctx) + { + fz_drop_display_list(ctx, dlist); + } + fz_catch(ctx) + { + abandon_widget_op(ctx, widget); + fz_rethrow(ctx); + } +} + +void pdf_drop_signer(fz_context *ctx, pdf_pkcs7_signer *signer) +{ + if (signer) + signer->drop(ctx, signer); +} + +void pdf_drop_verifier(fz_context *ctx, pdf_pkcs7_verifier *verifier) +{ + if (verifier) + verifier->drop(ctx, verifier); +} + +char *pdf_signature_error_description(pdf_signature_error err) +{ + switch (err) + { + case PDF_SIGNATURE_ERROR_OKAY: + return "OK"; + case PDF_SIGNATURE_ERROR_NO_SIGNATURES: + return "No signatures."; + case PDF_SIGNATURE_ERROR_NO_CERTIFICATE: + return "No certificate."; + case PDF_SIGNATURE_ERROR_DIGEST_FAILURE: + return "Signature invalidated by change to document."; + case PDF_SIGNATURE_ERROR_SELF_SIGNED: + return "Self-signed certificate."; + case PDF_SIGNATURE_ERROR_SELF_SIGNED_IN_CHAIN: + return "Self-signed certificate in chain."; + case PDF_SIGNATURE_ERROR_NOT_TRUSTED: + return "Certificate not trusted."; + default: + case PDF_SIGNATURE_ERROR_UNKNOWN: + return "Unknown error."; + } +} + +void pdf_signature_drop_distinguished_name(fz_context *ctx, pdf_pkcs7_distinguished_name *dn) +{ + if (dn) + { + fz_free(ctx, dn->c); + fz_free(ctx, dn->email); + fz_free(ctx, dn->ou); + fz_free(ctx, dn->o); + fz_free(ctx, dn->cn); + fz_free(ctx, dn); + } +} + +char *pdf_signature_format_distinguished_name(fz_context *ctx, pdf_pkcs7_distinguished_name *name) +{ + const char *parts[] = { + "cn=", "", + ", o=", "", + ", ou=", "", + ", email=", "", + ", c=", ""}; + size_t len = 1; + char *s; + int i; + + if (name == NULL) + return NULL; + + parts[1] = name->cn; + parts[3] = name->o; + parts[5] = name->ou; + parts[7] = name->email; + parts[9] = name->c; + + for (i = 0; i < (int)nelem(parts); i++) + if (parts[i]) + len += strlen(parts[i]); + + s = fz_malloc(ctx, len); + s[0] = '\0'; + + for (i = 0; i < (int)nelem(parts); i++) + if (parts[i]) + fz_strlcat(s, parts[i], len); + + return s; +} + +pdf_pkcs7_distinguished_name *pdf_signature_get_widget_signatory(fz_context *ctx, pdf_pkcs7_verifier *verifier, pdf_annot *widget) +{ + if (!widget->page) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation not bound to any page"); + return pdf_signature_get_signatory(ctx, verifier, widget->page->doc, widget->obj); +} + +pdf_pkcs7_distinguished_name *pdf_signature_get_signatory(fz_context *ctx, pdf_pkcs7_verifier *verifier, pdf_document *doc, pdf_obj *signature) +{ + char *contents = NULL; + size_t contents_len; + pdf_pkcs7_distinguished_name *dn; + + if (pdf_dict_get_inheritable(ctx, signature, PDF_NAME(FT)) != PDF_NAME(Sig)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation is not a signature widget"); + if (!pdf_signature_is_signed(ctx, doc, signature)) + return NULL; + + contents_len = pdf_signature_contents(ctx, doc, signature, &contents); + if (contents_len == 0) + return NULL; + + fz_try(ctx) + dn = verifier->get_signatory(ctx, verifier, (unsigned char *)contents, contents_len); + fz_always(ctx) + fz_free(ctx, contents); + fz_catch(ctx) + fz_rethrow(ctx); + + return dn; +} + +pdf_signature_error pdf_check_widget_digest(fz_context *ctx, pdf_pkcs7_verifier *verifier, pdf_annot *widget) +{ + if (!widget->page) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation not bound to any page"); + return pdf_check_digest(ctx, verifier, widget->page->doc, widget->obj); +} + +pdf_signature_error pdf_check_digest(fz_context *ctx, pdf_pkcs7_verifier *verifier, pdf_document *doc, pdf_obj *signature) +{ + pdf_signature_error result = PDF_SIGNATURE_ERROR_UNKNOWN; + fz_stream *bytes = NULL; + char *contents = NULL; + size_t contents_len; + + if (pdf_dict_get_inheritable(ctx, signature, PDF_NAME(FT)) != PDF_NAME(Sig)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation is not a signature widget"); + if (!pdf_signature_is_signed(ctx, doc, signature)) + return PDF_SIGNATURE_ERROR_NOT_SIGNED; + + contents_len = pdf_signature_contents(ctx, doc, signature, &contents); + + fz_var(bytes); + fz_try(ctx) + { + bytes = pdf_signature_hash_bytes(ctx, doc, signature); + result = verifier->check_digest(ctx, verifier, bytes, (unsigned char *)contents, contents_len); + } + fz_always(ctx) + { + fz_drop_stream(ctx, bytes); + fz_free(ctx, contents); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + + return result; +} + +pdf_signature_error pdf_check_widget_certificate(fz_context *ctx, pdf_pkcs7_verifier *verifier, pdf_annot *w) +{ + if (!w->page) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation not bound to any page"); + return pdf_check_certificate(ctx, verifier, w->page->doc, w->obj); +} + +pdf_signature_error pdf_check_certificate(fz_context *ctx, pdf_pkcs7_verifier *verifier, pdf_document *doc, pdf_obj *signature) +{ + char *contents = NULL; + size_t contents_len; + pdf_signature_error result = PDF_SIGNATURE_ERROR_UNKNOWN; + + if (pdf_dict_get_inheritable(ctx, signature, PDF_NAME(FT)) != PDF_NAME(Sig)) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation is not a signature widget"); + if (!pdf_signature_is_signed(ctx, doc, signature)) + return PDF_SIGNATURE_ERROR_NOT_SIGNED; + + contents_len = pdf_signature_contents(ctx, doc, signature, &contents); + + fz_try(ctx) + result = verifier->check_certificate(ctx, verifier, (unsigned char *)contents, contents_len); + fz_always(ctx) + fz_free(ctx, contents); + fz_catch(ctx) + fz_rethrow(ctx); + return result; +}
