在基于Linux的发行版上签署Windows应用程序

35

我已经准备好一个应用程序和网站,顾客可以在下载前为这个应用程序设置几个选项。设置以二进制格式存储在文件末尾(追加),然后编辑过的文件被发送到终端用户。问题是文件内容的更改会破坏文件签名 - 是否有任何命令行工具可以重新签名这个更改后的文件?我尝试使用微软的SignTool,但在Linux上它不能正常工作。

3个回答

53
你可以尝试使用 osslsigncode 来进行签名。
要签署一个EXE或MSI文件,现在可以执行以下操作:
osslsigncode sign -certs <cert-file> -key <der-key-file> \
        -n "Your Application" -i http://www.yourwebsite.com/ \
        -in yourapp.exe -out yourapp-signed.exe

如果您同时使用带密码的PEM或PVK密钥文件和PEM证书:
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

如果您想添加时间戳:
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

您可以使用存储在PKCS#12容器中的证书和密钥:
osslsigncode sign -pkcs12 <pkcs12-file> -pass <pkcs12-password> \
        -n "Your Application" -i http://www.yourwebsite.com/ \
        -in yourapp.exe -out yourapp-signed.exe

签署包含Java类文件的CAB文件:

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

这是对我有效的解决方案。Signcode工具没有签署该文件(尽管它报告签署成功)。 - treaz
这将对我很有用,因为我可以使用NSIS和Ant在Mageia Linux下创建的Windows安装程序(包装Java软件)进行签名。非常感谢 :) - gouessej
我很高兴找到了这个好的解决方案!实际上,我想使用Windows中的其他工具来签署我的代码,所以在阅读了你的答案后,我使用了Windows上的Ubuntu Bash。如果还有其他人遇到同样的问题,可以参考这篇文章 https://blog.synapp.nz/2017/06/16/code-signing-a-windows-application-on-linux-on-windows/ - Stacey Richards
这是2022年,它仍然非常有效。 - Sthe
感谢你指引我去使用osslsigncode,我给你点赞! - undefined

31

使用Mono的signtool对可执行文件进行签名实际上是相当简单的;复制证书以正确格式从Windows复制到Linux是有点棘手的(在链接的Mozilla文章中有更详细的描述)。

将Windows PFX证书文件转换为PVK和SPC文件,只需要在将证书从Windows复制到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

实际上,签署exe文件很简单;
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

你有使用过这个工具的个人经验吗?你提到的那篇文章已经超过2年了,所以能够确认它是否仍然是最新的会很好。 - Rob W
@RobW 我已经使用Mono 3.2.5使用该命令签署了可执行文件,它运行良好(事实上我刚刚测试过)。我现在无法测试从Windows导出证书的确切步骤,因为我不在Mac上,但我知道给出的流程非常类似于我最近使用的流程。 - Joachim Isaksson
感谢确认!不用担心证书,OpenSSL 能够将任何东西转换为任何格式。 - Rob W
当你重新输入时,可能会出现错误: openssl:Error: 'cr12pkcs7' is an invalid command.请确保注意第三个字符是小写字母'L'而不是数字'1'。 - user391035
@user391035 请检查您的拼写。正确的是 crl2pkcs7,其中 L 不是数字 1。 - Tenders McChiken

3
如果您想在运行时以编程方式执行此操作,可以使用Jsign工具。特别是当您在后端生成自执行存档并在签名请求后进行签名时,它可能非常有用。而且,显然要使用Java/Kotlin(工具名称就是这个意思)。以下是官方网站提供的API:

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.

除了使用Java KeyStore 进行签名外,AuthenticodeSigner 还有一个 (Certificate, PrivateKey) 构造函数,您可以像我在我的后端 "Spring on Kotlin" 中所做的那样自由地使用它。
    @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");


在此之后,您只需@Autowire签名者bean并调用其方法sign()即可使用所需文件。

1
Jsign 工具比 osslsigncode 工具更好,因为您可以轻松添加签名。我在使用 osslsigncode 时无法做到这一点,它总是用新的签名替换先前的签名。请注意,在我的 CentOS 系统上,/bin/jsign 命令行工具存在一些新行问题(可能是在 Windows 上创建的)。因此,我创建了一个具有相同内容的新工具,然后它完美无缺地运行了。 - Gabriel Hautclocq
最新版本已经修复了换行问题。 - Emmanuel Bourg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接