On 10/18/24 20:55, Martin Balao wrote: > Our understanding is that in such an event the Filter would be > initialized when the ClassLoader does JAR verification. If this is the > case, programmatically defining a Filter in the signed JAR should not > work. @Francisco will create a proof-of-concept to confirm this > hypothesis in the coming days. > Hi Sean,
I confirmed it, programmatically defining a Filter in a signed JAR doesn't work (full test at the end of this email). In such cases, the Filter initialization occurs at the following point: java.lang.Exception: Stack trace at java.base/sun.security.jca.ProvidersFilter.<clinit>(ProvidersFilter.java:681) at java.base/java.security.Provider$Service.computeSvcAllowed(Provider.java:2807) at java.base/java.security.Provider$ServicesMap$ServicesMapImpl.putService(Provider.java:1099) at java.base/java.security.Provider.putService(Provider.java:2252) at java.base/sun.security.provider.Sun.putEntries(Sun.java:75) at java.base/sun.security.provider.Sun.<init>(Sun.java:61) at java.base/sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:183) at java.base/sun.security.jca.ProviderList.getProvider(ProviderList.java:271) at java.base/sun.security.jca.ProviderList.getIndex(ProviderList.java:301) at java.base/sun.security.jca.ProviderList.getProviderConfig(ProviderList.java:285) at java.base/sun.security.jca.ProviderList.getProvider(ProviderList.java:291) at java.base/sun.security.jca.Providers.startJarVerification(Providers.java:110) at java.base/sun.security.util.SignatureFileVerifier.<init>(SignatureFileVerifier.java:110) at java.base/java.util.jar.JarVerifier.processEntry(JarVerifier.java:301) at java.base/java.util.jar.JarVerifier.update(JarVerifier.java:232) at java.base/java.util.jar.JarFile.initializeVerifier(JarFile.java:760) at java.base/java.util.jar.JarFile.getInputStream(JarFile.java:858) at java.base/jdk.internal.loader.URLClassPath$JarLoader$2.getInputStream(URLClassPath.java:837) at java.base/jdk.internal.loader.Resource.cachedInputStream(Resource.java:77) at java.base/jdk.internal.loader.Resource.getByteBuffer(Resource.java:163) at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:853) at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760) at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681) at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Class.java:578) at java.base/java.lang.Class.forName(Class.java:557) at java.base/sun.launcher.LauncherHelper.loadMainClass(LauncherHelper.java:841) at java.base/sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:736) > We agree that throwing an exception in such a case would be ideal. > However, this type of change —as we see it— involves a specification > change in System::setProperty/Security::setProperty as that's the only > interface to set a Filter value programmatically. We are open to having > this discussion. > > Another alternative would be to reserve the property in > System::setProperty/Security::setProperty and throw an exception > irrespective of the Filter status. We think that setting the Filter > programmatically should not be recommended for general use. However, > this would prevent advanced users that know how things are initialized > from benefiting. __ jar_signing_test.sh _________________________________________________ #!/usr/bin/env bash export JAVA_HOME="$(realpath build/*/images/jdk)" [ -d "$JAVA_HOME" ] || exit 1 echo "Creating jar files..." cat <<'EOF' >Main.java import javax.crypto.Cipher; import java.security.NoSuchAlgorithmException; public final class Main { public static void main(String[] args) throws Exception { System.setProperty("jdk.security.providers.filter", "*.Cipher.*/CBC/*; !*.Cipher.*; *"); // Allowed Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); cipher.getProvider(); // Denied try { cipher = Cipher.getInstance("AES/ECB/NoPadding"); throw new Error("Should have thrown (denied)!"); } catch (NoSuchAlgorithmException expected) {} System.out.println("The filter is working properly."); } } EOF $JAVA_HOME/bin/javac Main.java $JAVA_HOME/bin/jar cfe unsigned.jar Main Main.class || exit 1 $JAVA_HOME/bin/jar cfe signed.jar Main Main.class $JAVA_HOME/bin/keytool -keystore ks -storepass testpwd \ -keypass testpwd -dname CN=test -alias test -genkeypair \ -keyalg EC &>/dev/null $JAVA_HOME/bin/jarsigner -keystore ks -storepass testpwd \ signed.jar test &>/dev/null || exit 1 rm -f ks Main.class Main.java echo -e "\nUnsigned jar file\n=================" $JAVA_HOME/bin/java -Djava.security.debug=jca -jar unsigned.jar 2> \ >(grep -m1 ^ProvidersFilter) echo -e "\nSigned jar file\n===============" $JAVA_HOME/bin/java -Djava.security.debug=jca -jar signed.jar 2> \ >(grep '^ProvidersFilter\|java\.lang\.Error') rm -f signed.jar unsigned.jar unset JAVA_HOME ________________________________________________________________________ __ OUTPUT ______________________________________________________________ Creating jar files... Unsigned jar file ================= ProvidersFilter: Parsing: *.Cipher.*/CBC/*; !*.Cipher.*; * The filter is working properly. Signed jar file =============== ProvidersFilter: No filter Exception in thread "main" java.lang.Error: Should have thrown (denied)! ________________________________________________________________________ Regards, -- Francisco