Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign exe before MSI installer is created #138

Open
danielpeintner opened this issue Apr 13, 2023 · 5 comments
Open

Sign exe before MSI installer is created #138

danielpeintner opened this issue Apr 13, 2023 · 5 comments
Labels
enhancement New feature or request

Comments

@danielpeintner
Copy link

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!

@vewert
Copy link

vewert commented Apr 13, 2023

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

@hakanai hakanai added the enhancement New feature or request label Apr 14, 2023
@hakanai
Copy link
Collaborator

hakanai commented Apr 14, 2023

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, jpackage just has additional flags.
      A future jpackage may gain the same for Windows for parity.)

@danielpeintner
Copy link
Author

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, jpackage just has additional flags.
    A future jpackage may gain the same for Windows for parity.)

@hakanai see above for options I need to sign..

@DJViking
Copy link
Collaborator

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@\@projectName@.exe")
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@\@projectName@.exe"

Dim SignExec : Set SignExec = WshShell.Exec(signCommand)
While SignExec.Status = WshRunning
    WScript.Sleep 50
Wend

Dim AttribOnExec : Set AttribOnExec = WshShell.Exec("attrib +r @projectName@\@projectName@.exe")

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>

@vewert
Copy link

vewert commented Apr 14, 2023

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")
      }
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants