Sign exe before MSI installer is created
The task jpackageImage creates an application image.
The task jpackage creates an MSI Installer of the application image (at least on Windows).
Requirement: I need to sign the executable which is wrapped in the MSI installer.
I can sign the MSI installer after the task jpackage is finished. However, virus scanner seem to need a signed executable in some cases as well. Hence I tried to first run jpackageImage , afterwards I signed the executable and after that I called jpackage to pack the result.
Unfortunately jpackage depends on jpackageImage and creates again a new application image which is not signed. How can I achieve that the already existing application image is used instead of creating a new one.
Thanks for any hint!
I sign it within the jpackageImageTask (kotlin dsl):
/**
* Extends the jpackageImage task.
* For Windows, signs the exe file
*/
tasks.jpackageImage {
when (osdetector.os) {
Os.WINDOWS.osName -> {
doLast {
logger.info("Windows jpackageImage")
//Add signing code here
}
}
else -> {
doLast {
logger.info("Other OS")
}
}
}
}
Note, you can ignore the osdetector stuff, I have that in because I also build for macOS.
Hopefully, that helps
I'm adding this as enhancement because, although really it was just a question, similar plugins do just have a simple way to get things signed as they are built, and, in some cases, jpackage itself can be instructed to do signing.
Things to think about:
- Assuming this will be implemented using
signtool:- Which parameters are necessary?
- Which optional parameters are worth adding?
- How should the overall API look?
(Try to fit with the way it will work for macOS - on macOS,
jpackagejust has additional flags. A future jpackage may gain the same for Windows for parity.)
Thanks @vewert for your input 👍 It helped me to find a solution that worked for me.
I have an external bat script that I would like to start. Unfortunately with your solution I did not manage to start the script within tasks.jpackageImage (Note: I am a noob in Gradle).
The solution I found is very similar. For the record I post it below...
def os = org.gradle.internal.os.OperatingSystem.current()
task runExecutableSign(type:Exec) {
doFirst {
println "Start Executable signing process ..."
workingDir = file('./_sign/')
commandLine = ['cmd', '/C', 'start', 'jsign-exe.bat']
// cmd /C start D:/XXX/_sign/jsign-exe.bat
}
}
if (os.windows) {
jpackageImage.finalizedBy runExecutableSign
}
The jsign-exe.bat is very simple. The only issue I encountered was that the exe file was set to read-only. Hence you need to remove this flag and than sign it with your tools. Something like this
:: remove read-only flag
attrib -r "...foo.exe"
:: sign EXE
"%PROGRAMFILES(X86)%\Windows Kits\10\App Certification Kit\signtool.exe" sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a "...foo.exe"
Hope this might be useful for others.
- How should the overall API look? (Try to fit with the way it will work for macOS - on macOS,
jpackagejust has additional flags. A future jpackage may gain the same for Windows for parity.)
@hakanai see above for options I need to sign..
jpackage builds both an MSI and an EXE for Windows.
To sign the EXE I have this small task:
ext {
signTool = "C:\\Program Files (x86)\\Windows Kits\\10\\App Certification Kit\\signtool.exe"
}
task signWindowsInstaller(type: Exec) {
executable = "${signTool}"
args = [
'sign', '/v',
'/d', project.name,
'/f', "${signCertificate}",
'/p', "${signPassword}",
'/fd', 'SHA256',
'/t', 'http://timestamp.digicert.com',
"${buildDir}\\native\\${project.name}-${project.version}.exe",
"${buildDir}\\native\\${project.name}-${project.version}.msi"
]
}
In order to sign the EXE within the MSI, I have overridden the package resource Post-image script, project-name-post-image.wsf. This is a custom script that is executed after the application image is created and before the MSI installer is built for both .msi and .exe packages.
https://docs.oracle.com/en/java/javase/16/jpackage/override-jpackage-resources.html#GUID-405708DC-0243-49FC-84D9-B2A7F0A011A9
<?xml version="1.0" ?>
<job>
<script language="VBScript">
Const WshRunning = 0
Const WshFinished = 1
Const WshFailed = 2
Set WshShell = CreateObject("WScript.Shell")
Dim AttribOffExec : Set AttribOffExec = WshShell.Exec("attrib -r @projectName@\@[email protected]")
While AttribOffExec.Status = WshRunning
WScript.Sleep 50
Wend
signCommand = """@signTool@"" sign /v /a /d ""@projectName@"" /f ""@signCertificate@"" /p @signPassword@ /fd SHA256 /t http://timestamp.digicert.com @projectName@\@[email protected]"
Dim SignExec : Set SignExec = WshShell.Exec(signCommand)
While SignExec.Status = WshRunning
WScript.Sleep 50
Wend
Dim AttribOnExec : Set AttribOnExec = WshShell.Exec("attrib +r @projectName@\@[email protected]")
Dim output
If SignExec.Status = WshFailed Then
output = SignExec.StdErr.ReadAll
Else
output = SignExec.StdOut.ReadAll
End If
Dim StdOut : Set StdOut = CreateObject("Scripting.FileSystemObject").GetStandardStream(1)
Stdout.Write output
</script>
</job>
I used to use signtool, but now I use the jsign plugin: https://ebourg.github.io/jsign/ It can be called from within the jpackageImage task.
I also noticed about the read-only problem (sorry I should have mentioned that problem).
Here is my more complete code, including jsign and removing read-only:
/**
* Extends the jpackageImage task.
* For Windows, signs the exe file
*/
tasks.jpackageImage {
when (osdetector.os) {
Os.WINDOWS.osName -> {
doLast {
logger.info("Windows jpackageImage")
Paths.get(jpackageWinDir.absolutePath, "${project.name}.exe").toFile().setWritable(true)
val jsign = project.extensions.getByName("jsign") as groovy.lang.Closure<*>
jsign(
"file" to Paths.get(jpackageWinDir.absolutePath, "${project.name}.exe").toString(),
"name" to longName,
"url" to projectUrl,
"keystore" to pfxFile.absolutePath,
"storepass" to pfxPass,
"alg" to signingAlg,
"tsaurl" to tsaUrl,
"tsmode" to tsMode
)
}
}
else -> {
doLast {
logger.info("Other OS")
}
}
}
}