In the smartmontools project, we successfully use NSIS since 2006. We are currently testing code signing of the installer exe via signpath.org.
A closer look comparing installed files from unsigned and signed version of the installer shows that the uninstaller from the signed installer has bogus header fields: an invalid checksum and a nonempty "Security Directory" located far behind EOF.
This is likely because the uninstaller shares its stub code with the installer itself and therefore also inherits the header changed by signing. The bogus uninstaller works, but could be considered as corrupt by malware scanners.
Interestingly if 7-Zip is used to unpack such a signed installer, it creates "uninstall.exe.nsis" instead of "uninstall.exe". This file apparently contains the non-stub part of the uninstaller.
I added this command to the nsi file:
!uninstfinalize "echo uninstfinalize %1"
This does not touch the uninstaller but fixes the problem: The uninstaller header is valid again. The size of the installer exe increases as expected because the stubs could no longer be shared.
If would suggest to document this positive side effect in the user manual and, of course, please keep it in future versions :-)
BTW: Signing reduced false positives at VirusTotal from 7/68 to 3/68.
That's interesting. NSIS does its own CRC calculation and appends it to the end of the installer, so if the signtool is really changing the installer stub then the installer would fail the integrity check when it runs, unless the signtool is recalculating and writing a new CRC value to the end of the installer, which would mean the uninstaller would also fail.
I don't know if 7-Zip has changed their code to deal with installers that use !uninstfinalize yet (the exehead code differs a bit as there is only one block of data instead of two in the generated installer).
Side note: in the script, I suggest using the EnVar plugin for environment variables instead of 'AddToPath', so that you don't have to deal with the string limit manually in the script.
A diff of
objdump -x -soutputs of unsigned and signed installer looks like:The signatures are located in the "Security Directory" area. The exe size increase is exactly the mentioned
00002f30. Other bytes are not changed.Test show that also modifying other header fields like Timestamp or DllCharacteristics do not break the installer. This proves that at least these fields are not included in the CRC.
Modifying one byte in a code section results in the expected "Installer integrity check has failed".
Re: Site note: I'm aware of the useful EnvVar plugin. I did not consider to use it yet as it is still not included in the Debian/Ubuntu NSIS packages we use for CI and release builds, see https://packages.debian.org/trixie/all/nsis-common/filelist
This is likely because it is also not included in the the official NSIS package for Windows. Perhaps this should be changed :-)
Oh I see. I don't have any experience with stuff outside of the code section, but I'm sure Anders will take a look at it.
But yes, I developed !uninstfinalize specifically for signing uninstallers, with a side effect of manipulating it with normal command line tools. I used to use 'cp' on linux to copy the uninstaller to my working directory, then to windows to verify it works standalone.