Signing Windows application on Linux-based distros

20,395

Solution 1

It's actually quite straight forward to do using Mono's signtool; the tricky part (described in more detail in the linked Mozilla article) is copying the certificate in the correct format from Windows to Linux.

Converting the Windows PFX certificate file to PVK and SPC files, only needs to be done once when copying the certificate from Windows to Linux;

openssl pkcs12 -in authenticode.pfx -nocerts -nodes -out key.pem
openssl rsa -in key.pem -outform PVK -pvk-strong -out authenticode.pvk
openssl pkcs12 -in authenticode.pfx -nokeys -nodes -out cert.pem
openssl crl2pkcs7 -nocrl -certfile cert.pem -outform DER -out authenticode.spc

Actually signing the exe is straight forward;

signcode \
 -spc authenticode.spc \
 -v authenticode.pvk \
 -a sha1 -$ commercial \
 -n My\ Application \
 -i http://www.example.com/ \
 -t http://timestamp.digicert.com/scripts/timstamp.dll \
 -tr 10 \
 MyApp.exe

Solution 2

You can try osslsigncode

To sign an EXE or MSI file you can now do:

osslsigncode sign -certs <cert-file> -key <der-key-file> \
        -n "Your Application" -i http://www.yourwebsite.com/ \
        -in yourapp.exe -out yourapp-signed.exe

or if you are using a PEM or PVK key file with a password together with a PEM certificate:

osslsigncode sign -certs <cert-file> \
        -key <key-file> -pass <key-password> \
        -n "Your Application" -i http://www.yourwebsite.com/ \
        -in yourapp.exe -out yourapp-signed.exe

or if you want to add a timestamp as well:

osslsigncode sign -certs <cert-file> -key <key-file> \
        -n "Your Application" -i http://www.yourwebsite.com/ \
        -t http://timestamp.verisign.com/scripts/timstamp.dll \
        -in yourapp.exe -out yourapp-signed.exe

You can use a certificate and key stored in a PKCS#12 container:

osslsigncode sign -pkcs12 <pkcs12-file> -pass <pkcs12-password> \
        -n "Your Application" -i http://www.yourwebsite.com/ \
        -in yourapp.exe -out yourapp-signed.exe

To sign a CAB file containing java class files:

osslsigncode sign -certs <cert-file> -key <key-file> \
        -n "Your Application" -i http://www.yourwebsite.com/ \
        -jp low \
        -in yourapp.cab -out yourapp-signed.cab

Solution 3

If you want to do that programmatically in runtime you can usee Jsign tool. Especially it could be quite helpful when you generate self-executable archive on the backend by request signing it after. And you do that using Java/Kotlin obviously (name of the tool is suggesting). Here is API provided from the official site:

Simply add this dependency to the project:

    <dependency>
      <groupId>net.jsign</groupId>
      <artifactId>jsign-core</artifactId>
      <version>3.1</version>
    </dependency> 

and then use the AuthenticodeSigner class like this:

 KeyStore keystore = KeyStoreUtils.load(newFile("keystore.p12"), "PKCS12", "password", null);

 AuthenticodeSigner signer = new AuthenticodeSigner(keystore, "test", "secret");  signer.withProgramName("My Application")
       .withProgramURL("http://www.example.com")
       .withTimestamping(true)
       .withTimestampingAuthority("http://timestamp.comodoca.com/authenticode");

 Signable file = Signable.of(new File("application.exe")); 
 signer.sign(file); 

See the Javadoc for more details about the API.

Besides signing via Java KeyStore AuthenticodeSigner has (Certificate, PrivateKey) constructor and you can freely use it like I did in my "Spring on Kotlin" backend:

    @Bean
    fun certsChain(): Array<Certificate> {
        val fact: CertificateFactory = CertificateFactory.getInstance("X.509")
        val `is` = ResourceUtil.getResourceFileAsInputStream("cert/certificate.pem")
        val cer: X509Certificate = fact.generateCertificate(`is`) as X509Certificate
        return arrayOf(cer)
    }

    @Bean
    fun privateKey(): PrivateKey {
        var key = ResourceUtil.getResourceFileAsString("cert/privateKey.pem")
        key = key.replace("-----BEGIN PRIVATE KEY-----", "")
        key = key.replace("\n", "")
        key = key.replace("-----END PRIVATE KEY-----", "")
        val encoded = Base64.getDecoder().decode(key)
        val kf = KeyFactory.getInstance("RSA")
        val keySpec = PKCS8EncodedKeySpec(encoded)
        return kf.generatePrivate(keySpec) as RSAPrivateKey
    }

    @Bean
    fun signer(
            certs: Array<Certificate>,
            privateKey: PrivateKey
    ): AuthenticodeSigner =
            AuthenticodeSigner(certs, privateKey)
                    .withProgramName("Your Company Name")
                    .withProgramURL("https://something.com")
                    .withTimestamping(true)
                    .withTimestampingAuthority("http://timestamp.comodoca.com/authenticode");

after, you can just @Autowire the signer bean and call its method sign() with the required file

Share:
20,395
Tomasz Banasiak
Author by

Tomasz Banasiak

I am passionate about new technologies and programming. I have over 5 years of commercial experience working as a programmer, I had the opportunity to participate in creating a number of projects aimed at the world market, many of which enjoyed strong success. I am happy to developing and improving my skills. I feel well both as a leader and part of a larger team. Contact me if you are looking for a person who is not afraid of new challenges. I'm a big fan of the tasks that are seemingly unsolvable, and to find ways to optimize existing algorithms.

Updated on February 24, 2021

Comments

  • Tomasz Banasiak
    Tomasz Banasiak over 3 years

    I have prepared an application and website where the customer can set several options for this application before he downloads it. Settings are stored in binary format on the end of the file (appended), then the edited file is sent to the end user. The problem is that the change of "contents" of the file will break the file signature - is there any chance to re-sign this changed file with any command line tools? I've tried to use Microsoft's SignTool, but it does not work properly on Linux.

  • Rob W
    Rob W over 10 years
    Have you had personal experience with using this tool? That article you're referring to is over 2 years old, so some assurance that it's still up-to-date would be nice.
  • Joachim Isaksson
    Joachim Isaksson over 10 years
    @RobW I've signed executables using that command using Mono 3.2.5 and it works well (in fact I just tested). I can't test the exact steps for exporting the certificate from Windows right now since I'm not on a Mac, but I do know the flow given to be very similar to what I've used recently.
  • Rob W
    Rob W over 10 years
    Thanks for the confirmation! Don't worry about the certificates, OpenSSL is capable of converting anything to anything.
  • treaz
    treaz almost 9 years
    This is the solution that has worked for me. The signcode tool did not sign the file (although it reported signing as successful)
  • gouessej
    gouessej over 7 years
    It will be useful for signing my Windows installers (wrapping Java softwares) created under Mageia Linux with NSIS and Ant. Thanks a lot :)
  • Stacey Richards
    Stacey Richards almost 7 years
    I'm glad I found this great solution! I actually wanted to use something other that Microsoft's signtool.exe within Windows to sign my code so I've used Bash on Ubuntu on Windows after reading your answer. If anyone else is in the same boat, here's the rundown blog.synapp.nz/2017/06/16/…
  • user391035
    user391035 almost 5 years
    You might get an error when retyping: openssl:Error: 'cr12pkcs7' is an invalid command. make sure you note that the third character is lower case 'L' and not number 1
  • Tenders McChiken
    Tenders McChiken about 3 years
    @user391035 check your spelling. It's crl2pkcs7 with an L and not a 1.
  • Gabriel Hautclocq
    Gabriel Hautclocq over 2 years
    The Jsign tool is better than the osslsigncode tool, because you can add signatures easily. I couldn't do that with osslsigncode, it always replaces the previous signature with the new one. Note that on my CentOS system, the /bin/jsign command line tool had some new line issue (it was probably created on Windows). So I made a new one with the same content, then it worked flawlessly.