ebourg / jsign

Java implementation of Microsoft Authenticode for signing Windows executables, installers & scripts
https://ebourg.github.io/jsign
Apache License 2.0
250 stars 107 forks source link

"Signature verification failed, the private key doesn't match the certificate" with certchain with 2 intermediate certs #161

Closed lanwen closed 1 year ago

lanwen commented 1 year ago

I'm trying to use GOOGLECLOUD HSM option with an EV certificate and it fails with 5.0 but works with 4.2

I'm trying to follow this guide https://icedev.pl/posts/setting-up-ev-code-signing-google-hsm-fips-140-2 and got already my EV cert chain in PKCS#7 in PEM. The chain contains 3 certs - the root one from GlobalSign, the intermediate one and mine.

Once I start signing with the following code:

new SignerHelper(new StdOutConsole(2), "options")
                .keystore("projects/%s/locations/%s/keyRings/%s".formatted(project, location, ring))
                .storetype("GOOGLECLOUD")
                .storepass(token)
                .alg("SHA-256")
                .alias(keyname)
                .keypass(pwd)
                .tsaurl("http://timestamp.globalsign.com/tsa/r6advanced1")
                .tsmode("RFC3161")
                .certfile("chain.p7b")
                .sign(new File("agent.exe"));

it works fine with 4.2, but with 5.0 produces the following exception:

java.security.SignatureException: Signature verification failed, the private key doesn't match the certificate
    at net.jsign.AuthenticodeSigner.verify(AuthenticodeSigner.java:492)
    at net.jsign.AuthenticodeSigner.createSignedData(AuthenticodeSigner.java:376)
    at net.jsign.AuthenticodeSigner.sign(AuthenticodeSigner.java:348)
    at net.jsign.SignerHelper.sign(SignerHelper.java:394)
    at net.jsign.JsignCLI.execute(JsignCLI.java:132)
    at net.jsign.JsignCLI.main(JsignCLI.java:40)

With debug I found, that in 5.0 there is a verification change: https://github.com/ebourg/jsign/compare/4.2...5.0#diff-385c6cc5266e53175e4e952d55124fb5876e082087a5746583a28397a6f742f0L367-L368

that grabs the first pubkey from the chain: https://github.com/ebourg/jsign/blob/master/jsign-core/src/main/java/net/jsign/AuthenticodeSigner.java#L476

I'm not sure how that works in 4.2 with the same pubkey, however with the chain containing 3 certs the assumption of the first key is wrong. For the obvious reason I don't have private key from the CA, but the last one is mine, and I do have the private key in HSM.

I verified my assumption replacing the chain[0] with chain[2] in the debugger and got a positive result.

Please let me know if I could provide any additional info.

ebourg commented 1 year ago

Jsign expects the signing certificate to appear first in the chain. The consistency of the private and public keys wasn't checked before, it's likely that Jsign 4.2 produced an invalid signature in the same situation. I plan to improve the selection of the signing certificate in the chain to make it work with any order.

ebourg commented 1 year ago

I've pushed the modification to sort automatically the certificate chain in the order expected. Could you give it a try?

lanwen commented 1 year ago

Could you share a hint on how to do that? I didn't find a snapshot for 5.1 here https://oss.sonatype.org/content/repositories/snapshots/net/jsign/jsign-core/ Nevermind, tested with my original p7b chain and it worked totally fine!

ebourg commented 1 year ago

Great! Thank you for the feedback. I'll release Jsign 5.1 in a few weeks, in the meantime you can build the snapshot from the sources (with mvn install -DskipTests).