Skip to content

Commit

Permalink
add support for adding VBA project signatures:
Browse files Browse the repository at this point in the history
- workbook: add function workbook_add_vba_project_signature incl. documentation
- packager: copy vbaProjectSignature.bin, create vbaProject.bin.rels
- add functional test test_macro_signed
- add examples/macro_signed
- fix examples/vbaProject.bin: was corrupted; now the button press works again; VBA project is signed by "VBA Code Signing"
- add examples/vbaProjectSignature.bin: contains "VBA Code Signing" certificate; based on dev/vba_code_signing/certificate.pfx (use import_certificate.ps1 to install on your computer to test VBA project is actually signed in Excel)
- add dev/vba_code_signing: utilities for creating, exporting, and importing VBA code signing certificates
  • Loading branch information
HolgiHo committed Sep 18, 2023
1 parent 44e72c5 commit 1d80620
Show file tree
Hide file tree
Showing 15 changed files with 263 additions and 3 deletions.
Binary file added dev/vba_code_signing/certificate.pfx
Binary file not shown.
11 changes: 11 additions & 0 deletions dev/vba_code_signing/create_and_export_certificate.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Generate new self-signed certificate for code signing (-Type CodeSigningCert) and add it to current user's certificates (Cert:\CurrentUser\My)
# The private key is allowed to be exported by specifying -KeyExportPolicy Exportable.
$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My -Subject "CN=VBA Code Signing" -KeyAlgorithm RSA -KeyLength 2048 -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -KeyExportPolicy Exportable -KeyUsage DigitalSignature -Type CodeSigningCert

# Export certificate and private key to .pfx file. The private key is protected by a password.
$pwd = ConvertTo-SecureString -String "xlsxwriter" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath certificate.pfx -Password $pwd

# Import the certificate also as trusted root certificate.
# A popup window will occur when not running as administrator. The warning should be confirmed.
Import-PfxCertificate certificate.pfx -CertStoreLocation Cert:\CurrentUser\Root -Exportable -Password $pwd
6 changes: 6 additions & 0 deletions dev/vba_code_signing/import_certificate.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# import certificate.pfx for current user
$pwd = ConvertTo-SecureString -String "xlsxwriter" -Force -AsPlainText
Import-PfxCertificate certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Exportable -Password $pwd

# import certificate.pfx also as trusted root certificate
Import-PfxCertificate certificate.pfx -CertStoreLocation Cert:\CurrentUser\Root -Exportable -Password $pwd
25 changes: 25 additions & 0 deletions docs/src/working_with_macros.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 a VBA macro signature file to a
libxlsxwriter file

VBA macros can be signed in Excel to allow for blocking execution of unsigned
macros in certain environments, for example.

Once the vbaProjectSignature.bin file has been extracted it can be added to
the libxlsxwriter workbook using the workbook_add_vba_project_signature()
function:

@code
workbook_add_vba_project_signature(workbook, "./vbaProjectSignature.bin");
@endcode

@note The name doesn't have to be `vbaProjectSignature.bin`. Any suitable
path/name for an existing VBA signature bin file will do.

See the full example at @ref macro_signed.c.


@section ww_macros_debugging What to do if it doesn't work

The libxlsxwriter test suite contains several tests to ensure that this feature
Expand Down
43 changes: 43 additions & 0 deletions examples/macro_signed.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*****************************************************************************
*
* An example of adding signed macros to a libxlsxwriter file using a VBA
* project file and a VBA project signature file extracted from an existing
* Excel .xlsm file.
*
* The vba_extract.py utility from the libxlsxwriter examples directory can be
* used to extract the vbaProject.bin file and the vbaProjectSignature.bin file
* (if the macros were signed).
*
* This example connects the macro to a button (the only Excel/VBA form object
* supported by libxlsxwriter) but that isn't a requirement for adding a macro
* file to the workbook.
*
* Copyright 2014-2021, John McNamara, [email protected]
*/

#include "xlsxwriter.h"

int main() {

/* Note the xlsm extension of the filename */
lxw_workbook *workbook = workbook_new("macro_signed.xlsm");
lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);

worksheet_set_column(worksheet, COLS("A:A"), 30, NULL);

/* Add a macro file extracted from an Excel workbook. */
workbook_add_vba_project(workbook, "vbaProject.bin");

/* Add a VBA project signature file extracted from an Excel workbook. */
workbook_add_vba_project_signature(workbook, "vbaProjectSignature.bin");

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);
}
Binary file modified examples/vbaProject.bin
Binary file not shown.
Binary file added examples/vbaProjectSignature.bin
Binary file not shown.
29 changes: 28 additions & 1 deletion include/xlsxwriter/workbook.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand All @@ -985,6 +986,32 @@ lxw_error workbook_validate_sheet_name(lxw_workbook *workbook,
lxw_error workbook_add_vba_project(lxw_workbook *workbook,
const char *filename);

/**
* @brief Add a vbaProjectSignature binary to the Excel workbook.
*
* @param workbook Pointer to a lxw_workbook instance.
* @param filename The path/filename of the vbaProjectSignature.bin file.
*
* The `%workbook_add_vba_project_signature()` function can be used to add
* a code signature to the VBA macros included via
* `workbook_add_vba_project()`. The binary signature file should have been
* extracted from an existing Excel xlsm file with signed macros.
*
* @code
* workbook_add_vba_project_signature(workbook, "vbaProjectSignature.bin");
* @endcode
*
* Only one `vbaProjectSignature.bin` file can be added per workbook. The name
* doesn't have to be `vbaProjectSignature.bin`. Any suitable path/name for an
* existing VBA project signature file will do.
*
* See also @ref working_with_macros
*
* @return A #lxw_error.
*/
lxw_error workbook_add_vba_project_signature(lxw_workbook *workbook,
const char *filename);

/**
* @brief Set the VBA name for the workbook.
*
Expand Down
89 changes: 87 additions & 2 deletions src/packager.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* | | |____ theme1.xml
* | |
* | |_____rels
* | |____ workbook.xml.rels
* | |____ workbook.xml.rels
* |
* |_____rels
* |____ .rels
Expand Down Expand Up @@ -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 <windows.h>
Expand Down Expand Up @@ -450,6 +450,37 @@ _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;
if (!workbook->vba_project)
return LXW_NO_ERROR; // no VBA project: do not add signature

/* 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.
*/
Expand Down Expand Up @@ -1244,6 +1275,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 && 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,
Expand Down Expand Up @@ -1637,6 +1672,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 || !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.
*/
Expand Down Expand Up @@ -1863,6 +1942,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);

Expand Down
31 changes: 31 additions & 0 deletions src/workbook.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -2657,6 +2658,36 @@ workbook_add_vba_project(lxw_workbook *self, const char *filename)
return LXW_NO_ERROR;
}

/*
* Add a vbaProjectSignature binary to the Excel workbook.
* Signature will only be included in file if workbook_add_vba_project is also called.
*/
lxw_error
workbook_add_vba_project_signature(lxw_workbook *self, const char *filename)
{
FILE *filehandle;

if (!filename) {
LXW_WARN("workbook_add_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(filename, "rb");
if (!filehandle) {
LXW_WARN_FORMAT1("workbook_add_vba_project_signature(): "
"file doesn't exist or can't be opened: %s.",
filename);
return LXW_ERROR_PARAMETER_VALIDATION;
}
fclose(filehandle);

self->vba_project_signature = lxw_strdup(filename);

return LXW_NO_ERROR;
}

/*
* Set the VBA name for the workbook.
*/
Expand Down
Binary file added test/functional/src/images/vbaProject05.bin
Binary file not shown.
Binary file not shown.
29 changes: 29 additions & 0 deletions test/functional/src/test_macro_signed.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*****************************************************************************
* Test cases for libxlsxwriter.
*
* Test to compare output against Excel files.
*
* Copyright 2014-2022, John McNamara, [email protected]
*
*/

#include "xlsxwriter.h"

int main() {

lxw_workbook *workbook = workbook_new("test_macro_signed.xlsm");
lxw_worksheet *worksheet = workbook_add_worksheet(workbook, "Foo");

workbook_add_vba_project(workbook, "images/vbaProject05.bin");
workbook_add_vba_project_signature(workbook, "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);
}
3 changes: 3 additions & 0 deletions test/functional/test_vba.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ def test_macro02(self):

def test_macro03(self):
self.run_exe_test('test_macro03', 'macro03.xlsm')

def test_macro_signed(self):
self.run_exe_test('test_macro_signed', 'macro_signed.xlsm')
Binary file added test/functional/xlsx_files/macro_signed.xlsm
Binary file not shown.

0 comments on commit 1d80620

Please sign in to comment.