From eb76eedac2a653d1563709fedd2c32871037db57 Mon Sep 17 00:00:00 2001 From: Holger Hoffmann <47777278+HolgiHo@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:37:38 +0200 Subject: [PATCH] Add signature file for VBA projects (2nd try) (#415) Add support for signed VBA projects. #415 --- docs/src/working_with_macros.dox | 25 +++++ include/xlsxwriter/workbook.h | 32 ++++++- src/packager.c | 87 +++++++++++++++++- src/workbook.c | 40 +++++++- test/functional/src/images/vbaProject05.bin | Bin 0 -> 15360 bytes .../src/images/vbaProject05Signature.bin | Bin 0 -> 2256 bytes test/functional/src/test_macro04.c | 30 ++++++ test/functional/test_vba.py | 3 + test/functional/xlsx_files/macro04.xlsm | Bin 0 -> 12875 bytes 9 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 test/functional/src/images/vbaProject05.bin create mode 100644 test/functional/src/images/vbaProject05Signature.bin create mode 100644 test/functional/src/test_macro04.c create mode 100644 test/functional/xlsx_files/macro04.xlsm diff --git a/docs/src/working_with_macros.dox b/docs/src/working_with_macros.dox index f60fb082..bf3d40a6 100644 --- a/docs/src/working_with_macros.dox +++ b/docs/src/working_with_macros.dox @@ -55,6 +55,10 @@ Python xlsxwriter module: Utility to extract a vbaProject.bin binary from an Excel 2007+ xlsm macro file ... +If the VBA project is signed, `vba_extract.py` also extracts the +`vbaProjectSignature.bin` file from the xlsm file. For adding a VBA project +signature see @ww_macros_signature. + @section ww_macros_adding Adding the VBA macros to a libxlsxwriter file @@ -143,6 +147,27 @@ clarity: @note This step is particularly important for macros created with non-English versions of Excel. + +@section ww_macros_signature Adding signed VBA macros to a libxlsxwriter file + +VBA macros can be signed in Excel to allow for blocking execution of unsigned +macros in certain environments, for example. + +The `vba_extract.py utility` can be used to extract the `vbaProject.bin` and +`vbaProjectSignature.bin` files from an existing xlsm file with signed macros. + +To add these files to the libxlsxwriter workbook use the +`workbook_add_signed_vba_project()` function: + +@code + workbook_add_signed_vba_project(workbook, + "./vbaProject.bin", "./vbaProjectSignature.bin"); +@endcode + +@note The names don't have to be `vbaProject.bin` and `vbaProjectSignature.bin`. +Any suitable paths/names for existing VBA project or signature files will do. + + @section ww_macros_debugging What to do if it doesn't work The libxlsxwriter test suite contains several tests to ensure that this feature diff --git a/include/xlsxwriter/workbook.h b/include/xlsxwriter/workbook.h index 7b1a59ee..f7840948 100644 --- a/include/xlsxwriter/workbook.h +++ b/include/xlsxwriter/workbook.h @@ -341,6 +341,7 @@ typedef struct lxw_workbook { lxw_hash_table *used_dxf_formats; char *vba_project; + char *vba_project_signature; char *vba_codename; lxw_format *default_url_format; @@ -966,7 +967,7 @@ lxw_error workbook_validate_sheet_name(lxw_workbook *workbook, * workbook_add_vba_project(workbook, "vbaProject.bin"); * @endcode * - * Only one `vbaProject.bin file` can be added per workbook. The name doesn't + * Only one `vbaProject.bin` file can be added per workbook. The name doesn't * have to be `vbaProject.bin`. Any suitable path/name for an existing VBA bin * file will do. * @@ -985,6 +986,35 @@ lxw_error workbook_validate_sheet_name(lxw_workbook *workbook, lxw_error workbook_add_vba_project(lxw_workbook *workbook, const char *filename); +/** + * @brief Add a vbaProject binary and a vbaProjectSignature binary to the Excel + * workbook. + * + * @param workbook Pointer to a lxw_workbook instance. + * @param vba_project The path/filename of the vbaProject.bin file. + * @param signature The path/filename of the vbaProjectSignature.bin file. + * + * The `%workbook_add_signed_vba_project()` function can be used to add digitally + * signed macros or functions to a workbook. The function adds a binary VBA project + * file and a binary VBA project signature file that have been extracted from an + * existing Excel xlsm file with digitally signed macros: + * + * @code + * workbook_add_signed_vba_project(workbook, "vbaProject.bin", "vbaProjectSignature.bin"); + * @endcode + * + * Only one `vbaProject.bin` file can be added per workbook. The name doesn't + * have to be `vbaProject.bin`. Any suitable path/name for an existing VBA bin + * file will do. The same applies for `vbaProjectSignature.bin`. + * + * See also @ref working_with_macros + * + * @return A #lxw_error. + */ +lxw_error workbook_add_signed_vba_project(lxw_workbook *workbook, + const char *vba_project, + const char *signature); + /** * @brief Set the VBA name for the workbook. * diff --git a/src/packager.c b/src/packager.c index c569a1d2..51f37c83 100644 --- a/src/packager.c +++ b/src/packager.c @@ -31,7 +31,7 @@ * | | |____ theme1.xml * | | * | |_____rels - * | |____ workbook.xml.rels + * | |____ workbook.xml.rels * | * |_____rels * |____ .rels @@ -81,7 +81,7 @@ STATIC lxw_error _write_vml_drawing_rels_file(lxw_packager *self, #ifdef _WIN32 /* Silence Windows warning with duplicate symbol for SLIST_ENTRY in local - * queue.h and widows.h. */ + * queue.h and windows.h. */ #undef SLIST_ENTRY #include @@ -450,6 +450,35 @@ _add_vba_project(lxw_packager *self) return LXW_NO_ERROR; } +/* + * Write the xl/vbaProjectSignature.bin file. + */ +STATIC lxw_error +_add_vba_project_signature(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_error err; + FILE *image_stream; + + if (!workbook->vba_project_signature) + return LXW_NO_ERROR; + + /* Check that the image file exists and can be opened. */ + image_stream = lxw_fopen(workbook->vba_project_signature, "rb"); + if (!image_stream) { + LXW_WARN_FORMAT1("Error adding vbaProjectSignature.bin to xlsx file: " + "file doesn't exist or can't be opened: %s.", + workbook->vba_project_signature); + return LXW_ERROR_CREATING_TMPFILE; + } + + err = _add_file_to_zip(self, image_stream, "xl/vbaProjectSignature.bin"); + fclose(image_stream); + RETURN_ON_ERROR(err); + + return LXW_NO_ERROR; +} + /* * Write the chart files. */ @@ -1244,6 +1273,10 @@ _write_content_types_file(lxw_packager *self) lxw_ct_add_override(content_types, "/xl/workbook.xml", LXW_APP_DOCUMENT "spreadsheetml.sheet.main+xml"); + if (workbook->vba_project_signature) + lxw_ct_add_override(content_types, "/xl/vbaProjectSignature.bin", + "application/vnd.ms-office.vbaProjectSignature"); + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { if (sheet->is_chartsheet) { lxw_snprintf(filename, LXW_FILENAME_LENGTH, @@ -1637,6 +1670,50 @@ _write_vml_drawing_rels_file(lxw_packager *self, lxw_worksheet *worksheet, return err; } +/* + * Write the vbaProject .rels xml file. + */ +STATIC lxw_error +_write_vba_project_rels_file(lxw_packager *self) +{ + lxw_relationships *rels; + lxw_workbook *workbook = self->workbook; + lxw_error err = LXW_NO_ERROR; + char *buffer = NULL; + size_t buffer_size = 0; + + if (!workbook->vba_project_signature) + return LXW_NO_ERROR; + + rels = lxw_relationships_new(); + if (!rels) { + err = LXW_ERROR_MEMORY_MALLOC_FAILED; + goto mem_error; + } + + rels->file = lxw_get_filehandle(&buffer, &buffer_size, self->tmpdir); + if (!rels->file) { + err = LXW_ERROR_CREATING_TMPFILE; + goto mem_error; + } + + lxw_add_ms_package_relationship(rels, "/vbaProjectSignature", + "vbaProjectSignature.bin"); + + lxw_relationships_assemble_xml_file(rels); + + err = _add_to_zip(self, rels->file, &buffer, &buffer_size, + "xl/_rels/vbaProject.bin.rels"); + + fclose(rels->file); + free(buffer); + +mem_error: + lxw_free_relationships(rels); + + return err; +} + /* * Write the _rels/.rels xml file. */ @@ -1863,6 +1940,12 @@ lxw_create_package(lxw_packager *self) error = _add_vba_project(self); RETURN_AND_ZIPCLOSE_ON_ERROR(error); + error = _add_vba_project_signature(self); + RETURN_AND_ZIPCLOSE_ON_ERROR(error); + + error = _write_vba_project_rels_file(self); + RETURN_AND_ZIPCLOSE_ON_ERROR(error); + error = _write_core_file(self); RETURN_AND_ZIPCLOSE_ON_ERROR(error); diff --git a/src/workbook.c b/src/workbook.c index f61681ff..a2d6cdbd 100644 --- a/src/workbook.c +++ b/src/workbook.c @@ -264,6 +264,7 @@ lxw_workbook_free(lxw_workbook *workbook) free(workbook->options.tmpdir); free(workbook->ordered_charts); free(workbook->vba_project); + free(workbook->vba_project_signature); free(workbook->vba_codename); free(workbook); } @@ -2638,7 +2639,7 @@ workbook_add_vba_project(lxw_workbook *self, const char *filename) if (!filename) { LXW_WARN("workbook_add_vba_project(): " - "filename must be specified."); + "project filename must be specified."); return LXW_ERROR_NULL_PARAMETER_IGNORED; } @@ -2646,7 +2647,7 @@ workbook_add_vba_project(lxw_workbook *self, const char *filename) filehandle = lxw_fopen(filename, "rb"); if (!filehandle) { LXW_WARN_FORMAT1("workbook_add_vba_project(): " - "file doesn't exist or can't be opened: %s.", + "project file doesn't exist or can't be opened: %s.", filename); return LXW_ERROR_PARAMETER_VALIDATION; } @@ -2657,6 +2658,41 @@ workbook_add_vba_project(lxw_workbook *self, const char *filename) return LXW_NO_ERROR; } +/* + * Add a vbaProject binary and a vbaProjectSignature binary to the Excel workbook. + */ +lxw_error +workbook_add_signed_vba_project(lxw_workbook *self, + const char *vba_project, + const char *signature) +{ + FILE *filehandle; + + lxw_error error = workbook_add_vba_project(self, vba_project); + if (error != LXW_NO_ERROR) + return error; + + if (!signature) { + LXW_WARN("workbook_add_signed_vba_project(): " + "signature filename must be specified."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + /* Check that the vbaProjectSignature file exists and can be opened. */ + filehandle = lxw_fopen(signature, "rb"); + if (!filehandle) { + LXW_WARN_FORMAT1("workbook_add_signed_vba_project(): " + "signature file doesn't exist or can't be opened: %s.", + signature); + return LXW_ERROR_PARAMETER_VALIDATION; + } + fclose(filehandle); + + self->vba_project_signature = lxw_strdup(signature); + + return LXW_NO_ERROR; +} + /* * Set the VBA name for the workbook. */ diff --git a/test/functional/src/images/vbaProject05.bin b/test/functional/src/images/vbaProject05.bin new file mode 100644 index 0000000000000000000000000000000000000000..4d134d759e71c632a217e35c0ca1187aaca1fc43 GIT binary patch literal 15360 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 literal 0 HcmV?d00001 diff --git a/test/functional/src/images/vbaProject05Signature.bin b/test/functional/src/images/vbaProject05Signature.bin new file mode 100644 index 0000000000000000000000000000000000000000..a2e96ddabb1ddcf4b1606947dce0f748f86a6839 GIT binary patch literal 2256 zcma!MWnj=@U|{fKW?*OsvO(Y?2ZRQZmw{}9Cf0B^POUbNw(q=*>`V)qSUrG3E=-J! zhI|G*Y#b07CRSDkgCI68Z8k<0#wK$nMwSIG22KWYNFuT<0-=2qA7n+lPxcnPtjBTp zI*)+@stGIt@AFa(cW&t^33`#pe8+oE+=3=%E`ug!HiIU{cMF)A7@3#^#H!jFtxB@@ z`yQS@<6)KVV*_5e^B6&{kT#Sw5NBf!WntzK2y=2&aL!LjRS3>Z&&$k9H;@zOH8M7^ zG_)`@Fg7=`i~@2^AY4-r7r*8vMkQp+7+D#Zn;7|lA;ieV)WpchaQY1Ef>hHRTjeH} z>^&U6)&GF@=Bwtw%q;y??)ZX%1w~nV#3OwSx;?BrcUK|Fn(75?Vb4D|Ic?eF3nZUeAQy%_G3o> zlXo(%i|p%v%=q}je4WrySMlSgZT=?ny}7p9KD^34#`^sy<(=G~kGIz9$FVJ!Td%eu z&(#01q43|$v-|jN_ARZoi?R0*Ik@A(}KF%zy}O}S$;;w|12!bOzaH?!XUmX3y%R88wW6nu(GlFRSVXSo zO5M#r@nd0B;DLMp{v3PW^8M~3X zEKTyWy{&sb!A0`XyN|J2t9qC7H$QNFXRV`dxAy1!Cid2i`7z!*T9kJDGu^lC$7*x; zuTf^VPk-6jv+~q*DTht7_VfPTxsN&gul}c#g-qHhs=79sQ+qFesr+zvYul%rli!Ah zdRn!fIQUA)&vwpJ5A8A!ukbz#uLX|v9zKuE3~N^`x1Ie$t>3@qyQ_{+&+R|DXSH_h zyY(ngd%-JjRh=J^?+SAnnE&2dHihj|^F}_sXOfj|iH&6iO&n$N+qDdv7*7LJ+i_s#GtkGI zRaR||CzBBy9X201K7l!hrHPS2@=I`?!lR!@oBp#rE1Op6Bevul-+m8Y zGbVTW1)2q=Z%uCn2RhDc{Hpznr@NXX)@1I9Gl~)!D_-uuzxh@QTYJgz!h({Uufw%& zq^w%5&lmY|-^9qbrcR%#3q>1#Pn7%99ie6A%oKQ~P5-D=;KB&bcxlyb?7Gs&Cx^}0 z!kqCr{Q<{o!PS3{9-MMnr>wLiQ|#N-$!B(S1@BOfEcoR3U(%yPM?n2x)UPF;7207Z zS?{E@Jl!x$^xm_iHU`6&*W``1ems53;c4Kz%B@=h{>2>qEo|0fbItmdu5IgoQ)YXA z_E)K*GJYwwFY8U#@Y>dUJ>beZ<@1om(=4e&`6Kh^a|vM|K}D>yYfuP3kY)s85g?9z u&9_PB@$8W7Q?KQ}EH9qf|IsgEz*G|0v literal 0 HcmV?d00001 diff --git a/test/functional/src/test_macro04.c b/test/functional/src/test_macro04.c new file mode 100644 index 00000000..c933c128 --- /dev/null +++ b/test/functional/src/test_macro04.c @@ -0,0 +1,30 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2022, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_macro04.xlsm"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, "Foo"); + + workbook_add_signed_vba_project(workbook, + "images/vbaProject05.bin", + "images/vbaProject05Signature.bin"); + + worksheet_set_column(worksheet, COLS("A:A"), 30, NULL); + worksheet_write_string(worksheet, 2, 0, "Press the button to say hello.", NULL); + + lxw_button_options options = { .caption = "Press Me", .macro = "say_hello", + .width = 80, .height = 30 }; + + worksheet_insert_button(worksheet, 2, 1, &options); + + return workbook_close(workbook); +} diff --git a/test/functional/test_vba.py b/test/functional/test_vba.py index 20a49516..751d72ee 100644 --- a/test/functional/test_vba.py +++ b/test/functional/test_vba.py @@ -21,3 +21,6 @@ def test_macro02(self): def test_macro03(self): self.run_exe_test('test_macro03', 'macro03.xlsm') + + def test_macro04(self): + self.run_exe_test('test_macro04', 'macro04.xlsm') diff --git a/test/functional/xlsx_files/macro04.xlsm b/test/functional/xlsx_files/macro04.xlsm new file mode 100644 index 0000000000000000000000000000000000000000..f24859e961f33835988398ce65e15950909f05d0 GIT binary patch literal 12875 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 literal 0 HcmV?d00001