How to use your own Bouncy Castle version in an application deployed on JBoss EAP 7.4

111 Views Asked by At

I have a classloading issue. My application is bundled as a EAR, made of a WAR + some EJB and JAR submodules. The EAR bundles its own version of Bouncy Castle Library, precisely two modules of that library:

  • bcprov-jdk18on-1.77.jar
  • bcpg-jdk18on-1.77.jar

JBoss EAP 7.4, instead, comes with its own version of Bouncy Castle, which is 1.68 in its "jdk15on" variant. For instance, the corresponding JARs are:

  • bcprov-jdk15on-1.68.0.redhat-00005.jar
  • bcpg-jdk15on-1.68.0.redhat-00005.jar

The problem I observe is that a mixture of classes from the two versions are used, and this leads to runtime errors like NoSuchMethodErrors. Here is a typical stack trace excerpt:

Caused by: java.lang.NoSuchMethodError: org.bouncycastle.crypto.engines.AESEngine.newInstance()Lorg/bouncycastle/crypto/MultiBlockCipher;
        at org.bouncycastle.openpgp.operator.bc.BcImplProvider.createBlockCipher(Unknown Source)
        at org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder$1.recoverKeyData(Unknown Source)
        at org.bouncycastle.openpgp.PGPSecretKey.extractKeyData(Unknown Source)
        at org.bouncycastle.openpgp.PGPSecretKey.extractPrivateKey(Unknown Source)

I have enabled the classloader verbose logging in JBoss and this is what I see:

2024-02-15 14:22:48,230 TRACE [org.jboss.modules] (Weld Thread Pool -- 1) Finding class org.bouncycastle.openpgp.PGPSecretKey from Module "deployment.myapp-ear.ear.myapp-ejb.jar" from Service Module Loader
2024-02-15 14:22:48,230 TRACE [org.jboss.modules] (Weld Thread Pool -- 1) Finding local class org.bouncycastle.openpgp.PGPSecretKey from Module "deployment.myapp-ear.ear" from Service Module Loader
2024-02-15 14:22:48,230 TRACE [org.jboss.modules] (Weld Thread Pool -- 1) Loading class org.bouncycastle.openpgp.PGPSecretKey locally from Module "deployment.myapp-ear.ear" from Service Module Loader

2024-02-15 14:27:33,162 TRACE [org.jboss.modules] (default task-35) Finding class org.bouncycastle.openpgp.operator.bc.BcImplProvider from Module "deployment.myapp-ear.ear" from Service Module Loader
2024-02-15 14:27:33,162 TRACE [org.jboss.modules] (default task-35) Finding local class org.bouncycastle.openpgp.operator.bc.BcImplProvider from Module "deployment.myapp-ear.ear" from Service Module Loader
2024-02-15 14:27:33,162 TRACE [org.jboss.modules] (default task-35) Loading class org.bouncycastle.openpgp.operator.bc.BcImplProvider locally from Module "deployment.myapp-ear.ear" from Service Module Loader
2024-02-15 14:27:33,162 TRACE [org.jboss.modules] (default task-35) Attempting to define class org.bouncycastle.openpgp.operator.bc.BcImplProvider in Module "deployment.myapp-ear.ear" from Service Module Loader
2024-02-15 14:27:33,162 TRACE [org.jboss.modules.define] (default task-35) Defined class org.bouncycastle.openpgp.operator.bc.BcImplProvider in Module "deployment.myapp-ear.ear" from Service Module Loader

2024-02-15 14:27:33,162 TRACE [org.jboss.modules] (default task-35) Finding local class org.bouncycastle.crypto.BlockCipher from Module "org.bouncycastle.bcprov" version 1.68.0.redhat-00005 from local module loader @4a11eb84 (finder: local module finder @4e858e0a (roots: /opt/rh/eap7/root/usr/share/wildfly/modules,/opt/rh/eap7/root/usr/share/wildfly/modules/system/layers/base))
2024-02-15 14:27:33,162 TRACE [org.jboss.modules] (default task-35) Loading class org.bouncycastle.crypto.BlockCipher locally from Module "org.bouncycastle.bcprov" version 1.68.0.redhat-00005 from local module loader @4a11eb84 (finder: local module finder @4e858e0a (roots: /opt/rh/eap7/root/usr/share/wildfly/modules,/opt/rh/eap7/root/usr/share/wildfly/modules/system/layers/base))
2024-02-15 14:27:33,163 TRACE [org.jboss.modules] (default task-35) Attempting to define class org.bouncycastle.crypto.BlockCipher in Module "org.bouncycastle.bcprov" version 1.68.0.redhat-00005 from local module loader @4a11eb84 (finder: local module finder @4e858e0a (roots: /opt/rh/eap7/root/usr/share/wildfly/modules,/opt/rh/eap7/root/usr/share/wildfly/modules/system/layers/base))
2024-02-15 14:27:33,163 TRACE [org.jboss.modules.define] (default task-35) Defined class org.bouncycastle.crypto.BlockCipher in Module "org.bouncycastle.bcprov" version 1.68.0.redhat-00005 from local module loader @4a11eb84 (finder: local module finder @4e858e0a (roots: /opt/rh/eap7/root/usr/share/wildfly/modules,/opt/rh/eap7/root/usr/share/wildfly/modules/system/layers/base))

2024-02-15 14:27:33,563 TRACE [org.jboss.modules] (default task-35) Finding class org.bouncycastle.crypto.engines.AESEngine from Module "deployment.myapp-ear.ear" from Service Module Loader
2024-02-15 14:27:33,563 TRACE [org.jboss.modules] (default task-35) Finding local class org.bouncycastle.crypto.engines.AESEngine from Module "org.bouncycastle.bcprov" version 1.68.0.redhat-00005 from local module loader @4a11eb84 (finder: local module finder @4e858e0a (roots: /opt/rh/eap7/root/usr/share/wildfly/modules,/opt/rh/eap7/root/usr/share/wildfly/modules/system/layers/base))
2024-02-15 14:27:33,564 TRACE [org.jboss.modules] (default task-35) Loading class org.bouncycastle.crypto.engines.AESEngine locally from Module "org.bouncycastle.bcprov" version 1.68.0.redhat-00005 from local module loader @4a11eb84 (finder: local module finder @4e858e0a (roots: /opt/rh/eap7/root/usr/share/wildfly/modules,/opt/rh/eap7/root/usr/share/wildfly/modules/system/layers/base))
2024-02-15 14:27:33,564 TRACE [org.jboss.modules] (default task-35) Attempting to define class org.bouncycastle.crypto.engines.AESEngine in Module "org.bouncycastle.bcprov" version 1.68.0.redhat-00005 from local module loader @4a11eb84 (finder: local module finder @4e858e0a (roots: /opt/rh/eap7/root/usr/share/wildfly/modules,/opt/rh/eap7/root/usr/share/wildfly/modules/system/layers/base))

So, as you can see:

  • PGPSecretKey from bcpg module is loaded from my own version of Bouncy Castle (1.77), bundled in the EAR
  • BcImplProvider from bcprov module is still loaded from my own version of Bouncy Castle (1.77), bundled in the EAR
  • BlockCipher and AESEngine from bcprov module are loaded from JBoss's version of Bouncy Castle (1.68)

I suspect that some code from JBoss itself is using Bouncy Castle 1.68 and this is causing the JBoss classloader to reuse classes from it whenever they are already in memory. But what I would like to achieve, instead, is a complete isolation (at least for this library) between the classes loaded to run JBoss and the ones loaded to run my application. Or at least a "child-first" approach when searching for a class which was not yet requested by my own application.

Please note that:

  • the EAR is built with Maven; the Bouncy Castle dependencies are compile dependencies, not provided ones; the BC 1.77 JARs are correctly bundled in the EAR
  • I already tried to put org.bouncycastle.bcprov and org.bouncycastle.bcpg among the excluded dependencies in jboss-deployment-structure.xml, with no luck; but I don't expect it to be needed anyway, since Bouncy Castle is not listed among the implicit module dependencies and seems to be a private module
  • following one suggestion given on another StackOverflow answer, I tried to add the two JARs as resource roots with use-physical-code-source set to true, once again in jboss-deployment-structure.xml, with no luck
  • please do not suggest to put the newer Bouncy Castle version on JBoss EAP's classpath: I'd like my application to use its bundled version without touching the server configuration, especially because other applications may still benefit from using the bundled (and older) Bouncy Castle version provided by JBoss EAP
1

There are 1 best solutions below

0
Mauro Molinari On

The solution in my case was to add an exclusion for the whole Bouncy Castle module, not just for the Bouncy Castle submodules I am using (org.bouncycastle.bcprov and org.bouncycastle.bcpg):

So in META-INF/jboss-deployment-structure.xml:

<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.3">
    <ear-exclusions-cascaded-to-subdeployments>true</ear-exclusions-cascaded-to-subdeployments>
    <deployment>
        <exclusions>
            <module name="org.bouncycastle"/>
        </exclusions>
    </deployment>
</jboss-deployment-structure>

With <ear-exclusions-cascaded-to-subdeployments> set to true I also ensure the exclusion is applied to all submodules.

I guess this might bue due to the fact that in our JBoss EAP installation the Bouncy Castle module as a whole seems to be made available to all deployed applications.