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

Jar Signing with Safenet Etoken - not working? #192

Closed scancmdr closed 5 months ago

scancmdr commented 5 months ago

I'm trying to sign a JAR file with a SafeNet etoken. I have the proper setup (latest safenet software, can list and sign exe files just fine with jsign). I've build jsign from the master branch and am using that jar.

I've read https://github.com/ebourg/jsign/discussions/178 and the docs at https://ebourg.github.io/jsign/#jca-provider

Java 8 using storetype ETOKEN

jarsigner -J-cp -Jlib/jsign-5.1-SNAPSHOT.jar:/opt/java8/lib/tools.jar -verbose:all -providerClass net.jsign.jca.JsignJcaProvider -keystore NONE -storetype ETOKEN -storepass REDACTED target/application.jar Sectigo_2023_REDACTED

jarsigner error: java.security.ProviderException: Failed to create a SunPKCS11 provider from the configuration --name=SafeNet eToken
library = "/usr/lib/pkcs11/libeTPkcs11.so"
slot=0

note: the pkcs libraries are simlinks to each other (/usr/lib/pkcs11/libeTPkcs11.so -> ../libeTPkcs11.so)

Java 8 using storetype PKCS11

jarsigner -J-cp -Jlib/jsign-5.1-SNAPSHOT.jar:/opt/java8/lib/tools.jar -verbose:all -providerClass net.jsign.jca.JsignJcaProvider -providerArg etoken.cfg -keystore NONE -storetype PKCS11 -storepass REDACTED target/application.jar Sectigo_2023_REDACTED

 updating: META-INF/MANIFEST.MF
jarsigner error: java.lang.ClassCastException: sun.security.pkcs11.P11Key$P11PrivateKey cannot be cast to net.jsign.jca.SigningServicePrivateKey

where etoken.cfg is:

name=eToken
library=/usr/lib/libeTPkcs11.so

Java 11 & 17 using storetype ETOKEN

jarsigner -J-cp -Jlib/jsign-5.1-SNAPSHOT.jar -J--add-modules -Jjava.sql -verbose:all -providerClass net.jsign.jca.JsignJcaProvider -keystore NONE -storetype ETOKEN -storepass REDACTED target/application.jar Sectigo_2023_REDACTED

Exception in thread "main" java.lang.IllegalAccessError: class net.jsign.SafeNetEToken (in unnamed module @0x2cb4c3ab) cannot access class sun.security.pkcs11.wrapper.PKCS11 (in module jdk.crypto.cryptoki) because module jdk.crypto.cryptoki does not export sun.security.pkcs11.wrapper to unnamed module @0x2cb4c3ab
        at net.jsign.SafeNetEToken.getTokenSlot(SafeNetEToken.java:72)
        at net.jsign.SafeNetEToken.getSunPKCS11Configuration(SafeNetEToken.java:58)
        at net.jsign.SafeNetEToken.getProvider(SafeNetEToken.java:43)
        at net.jsign.KeyStoreType$16.getProvider(KeyStoreType.java:410)
        at net.jsign.KeyStoreBuilder.provider(KeyStoreBuilder.java:268)
        at net.jsign.KeyStoreBuilder.build(KeyStoreBuilder.java:281)
        at net.jsign.jca.JsignJcaProvider$JsignJcaKeyStore.getKeyStore(JsignJcaProvider.java:99)
        at net.jsign.jca.JsignJcaProvider$JsignJcaKeyStore.engineAliases(JsignJcaProvider.java:132)
        at java.base/java.security.KeyStore.aliases(KeyStore.java:1262)
        at jdk.jartool/sun.security.tools.jarsigner.Main.loadKeyStore(Main.java:2120)
        at jdk.jartool/sun.security.tools.jarsigner.Main.run(Main.java:285)
        at jdk.jartool/sun.security.tools.jarsigner.Main.main(Main.java:132)
stolp commented 5 months ago

I was stumbling about this as well, and I think there is a small error in SafeNetEToken.java, line 56:

String configuration = "--name=SafeNet eToken\nlibrary = \"" + library.getAbsolutePath().replace("\\", "\\\\") + "\"\n";

should be changed to:

String configuration = "--name=\"SafeNet eToken\"\nlibrary = \"" + library.getAbsolutePath().replace("\\", "\\\\") + "\"\n";

Changing this let me sign my executable just fine. Hope this helps.

scancmdr commented 5 months ago

@stolp Thanks, that seems to take a step further, my Java 8 output is now:

jarsigner error: java.security.ProviderException: Failed to create a SunPKCS11 provider from the configuration --name=SafeNet eToken
library = "/usr/lib/pkcs11/libeTPkcs11.so"
slot=0

But I will try some variations on that configuration variable, as it is the config file I was using previously with jsign/jarsigner. I noticed that that line 56 is a bit inconsistent with --name= and library= -> no posix double hyphen in front of library. I tried a few variations with both params, still got that same error.

I'm closer I think.

susp248 commented 5 months ago

@scancmdr Please note that I only was successful using the jsign-maven-plugin to sign a windows executable yet. I was not yet successful at using the maven-jarsigner-plugin.

Using Java 21, I also added the following to .mvn/jvm.config in my project:

--add-exports jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED --add-exports jdk.crypto.cryptoki/sun.security.pkcs11.wrapper=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-exports java.base/sun.security.action=ALL-UNNAMED --add-exports java.base/sun.security.rsa=ALL-UNNAMED --add-opens java.base/sun.security.util=ALL-UNNAMED

ebourg commented 5 months ago

@scancmdr, @stolp: Thank you for the feedback, I don't have an eToken to test, so your input is very valuable.

I've fixed the name parameter in the SunPKCS11 configuration.

The Jsign JCA provider is designed to work with the signing services such as Azure or AWS. I didn't think it could be useful for PKCS11 tokens since using the SunPKCS11 provider directly is easier. For example with the SafeNet eToken the syntax should look like this:

jarsigner -providerClass sun.security.pkcs11.SunPKCS11 \
          -providerArg etoken.cfg \
          -keystore NONE \
          -storetype PKCS11 \
          -storepass secret \
           application.jar alias
ebourg commented 5 months ago

I've changed JsignJcaProvider to also work with the Safenet eToken. With Java 11 and later it requires an extra parameter:

-J--add-exports -Jjdk.crypto.cryptoki/sun.security.pkcs11.wrapper=ALL-UNNAMED

Let me know how it works for you.

stolp commented 5 months ago

@ebourg: I was now able to both sign my windows binary and the jars (using the above SunPKCS11 configuration approach).

I am not sure, is it worth to switch to JsignJcaProvider for jar signing?

Thank you very much for your quick support, looking forward to a release of version 5.1.

ebourg commented 5 months ago

I am not sure, is it worth to switch to JsignJcaProvider for jar signing?

I don't think it's worth switching. Using jarsigner with the SunPKCS11 provider instead of JsignJcaProvider means a simpler command line syntax and no extra jar. The only benefit of the Jsign provider is the automatic PKCS11 configuration.

scancmdr commented 5 months ago

@ebourg I'm still working on this, I will post my results, I'm working on two fronts:

  1. jarsigner with SunPKCS11 - which is my own issue and thank you for the guidance, nothing to do with jsign
  2. jsign with my sectigo safenet etoken for exe files - specifically for authenticode, so users don't get that warnings on downloads of executables (exe or msi) - I've had this working with jsign before with my last safenet token and it worked with Defender and Win security at max, so still figuring this with your new ETOKEN storetype

Likely unrelated is how to get a JAR download done on Windows to see some EV cert code signature and not complain, certainly possible for exe, but not so clear for JAR files that are simply executable (ala java -jar style).

If there is another test case I can do for jsign with my safenet etoken I'm happy to try and post my results for jsign.

ebourg commented 5 months ago

If there is another test case I can do for jsign with my safenet etoken I'm happy to try and post my results for jsign

Yes please, did you manage to sign the exe files with the ETOKEN storetype, or is there still an exception?

stolp commented 5 months ago

I am successfully signing an exe file using the maven-jsign-plugin and the ETOKEN method. All is good.

scancmdr commented 5 months ago

@stolp great news.

I have time tomorrow, please let me post my experience with my safenet token with .exe before we close. I've been using jsign for a long while.

scancmdr commented 5 months ago

My latest results for .exe files

Java 17

Looks good.

+ rm -rf HOSTNAME.EXE
+ cp /tmp/HOSTNAME.EXE .
+ stat -t HOSTNAME.EXE
HOSTNAME.EXE 36864 72 81ed 1000 1000 10304 10892990 1 0 0 1705155445 1705155445 1705155445 1705155445 4096
+ md5sum HOSTNAME.EXE
e1ea6eff1f45aa136a2a2e18eacc1f73  HOSTNAME.EXE
+ java -jar lib/jsign-5.1-SNAPSHOT.jar -t http://timestamp.sectigo.com --storetype ETOKEN --storepass REDACTED -a Sectigo_REDACTED HOSTNAME.EXE
Adding Authenticode signature to HOSTNAME.EXE
+ stat -t HOSTNAME.EXE
HOSTNAME.EXE 47224 96 81ed 1000 1000 10304 10892990 1 0 0 1705155448 1705155448 1705155448 1705155445 4096
+ md5sum HOSTNAME.EXE
d62a717118589e14a7e03fe0a270c37b  HOSTNAME.EXE

Windows likes it:

$ ./signtool.exe verify /pa HOSTNAME.EXE
File: HOSTNAME.EXE
Index  Algorithm  Timestamp
========================================
0      sha256     Authenticode

Successfully verified: HOSTNAME.EXE

Java 8 (Same jsign command)

+ java -jar lib/jsign-5.1-SNAPSHOT.jar -t http://timestamp.sectigo.com --storetype ETOKEN --storepass REDACTED -a Sectigo_REDACTED HOSTNAME.EXE
Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.limit(I)Ljava/nio/ByteBuffer;
    at net.jsign.pe.PEFile.read(PEFile.java:172)
    at net.jsign.pe.PEFile.<init>(PEFile.java:130)
    at net.jsign.pe.PEFile.<init>(PEFile.java:114)
    at net.jsign.pe.PEFile.isPEFile(PEFile.java:95)
    at net.jsign.Signable.of(Signable.java:141)
    at net.jsign.SignerHelper.sign(SignerHelper.java:374)
    at net.jsign.JsignCLI.execute(JsignCLI.java:133)
    at net.jsign.JsignCLI.main(JsignCLI.java:40)

I've seen this before, its not obvious from javadoc, but I believe the contract of some ByteBuffer methods changed in Java 9+ (returning ByteBuffer instead of Buffer, or vice versa). See this Apache issue. This could be related.

Java 8 (adjusted jsign)

+ jarsigner -J-cp -Jlib/jsign-5.1-SNAPSHOT.jar:/opt/java8/lib/tools.jar -verbose:all -providerClass net.jsign.jca.JsignJcaProvider -providerArg etoken.cfg -keystore NONE -storetype PKCS11 -storepass REDACTED HOSTNAME.EXE Sectigo_REDACTED
jarsigner error: java.lang.IllegalArgumentException: keystore parameter should either refer to the SunPKCS11 configuration file or to the name of the provider configured in jre/lib/security/java.security

I need to adjust the command line here, can't recall.

ebourg commented 5 months ago

Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.limit(I)Ljava/nio/ByteBuffer;

This is a common issue, building Jsign with Java 8 will fix it.

jarsigner -J-cp -Jlib/jsign-5.1-SNAPSHOT.jar:/opt/java8/lib/tools.jar -verbose:all -providerClass net.jsign.jca.JsignJcaProvider -providerArg etoken.cfg -keystore NONE -storetype PKCS11 -storepass REDACTED HOSTNAME.EXE Sectigo_REDACTED jarsigner error: java.lang.IllegalArgumentException: keystore parameter should either refer to the SunPKCS11 configuration file or to the name of the provider configured in jre/lib/security/java.security

Try this syntax instead (with a .jar file, not a .exe):

jarsigner -J-cp -Jlib/jsign.jar:/opt/java8/lib/tools.jar \
          -providerClass net.jsign.jca.JsignJcaProvider \
          -keystore NONE \
          -storetype ETOKEN \
          -storepass secret \
          application.jar alias
scancmdr commented 5 months ago

Yes that worked with Java 8:

$ jarsigner -tsa http://timestamp.sectigo.com \
          -J-cp -Jlib/jsign-5.1-SNAPSHOT.jar:/opt/java8/lib/tools.jar \
          -providerClass net.jsign.jca.JsignJcaProvider \
          -keystore NONE \
          -storetype ETOKEN \
          -storepass REDACTED \
          geek.jar Sectigo_REDACTED
jar signed.

The signer certificate will expire on 2026-12-24.
The timestamp will expire on 2034-08-02.

$ jarsigner -verify geek.jar

jar verified.

I was able to sort my intermediate cert issue by importing them into my etoken using the safenet config tool. @ebourg thanks very much for your help!

ebourg commented 5 months ago

@scancmdr Thank you for checking!