While using Apache Camel’s SSH component I’ve faced a strange issue. I was developing an application which could run in background and collect output of linux commands. From IDEA it was running ok, but when I created a fat .jar using maven-assembly-plugin and started it I got following exception:

[...    main] SecurityUtils      INFO  BouncyCastle not registered, using the default JCE provider
[...thread-1] ClientSessionImpl  INFO  Client session created
[...thread-1] ClientSessionImpl  INFO  Server version string: SSH-2.0-OpenSSH_4.3
[...thread-2] ClientSessionImpl  WARN  Exception caught
java.security.InvalidAlgorithmParameterException: DH key size must be multiple of 64, and can only range from 512 to 2048 (inclusive).
The specific key size 4096 is not supported
  at com.sun.crypto.provider.DHKeyPairGenerator.initialize(DHKeyPairGenerator.java:128)
  at java.security.KeyPairGenerator$Delegate.initialize(Unknown Source)
  at java.security.KeyPairGenerator.initialize(Unknown Source)
  at org.apache.sshd.common.kex.DH.getE(DH.java:65)
  at org.apache.sshd.client.kex.DHGEX.next(DHGEX.java:118)
  at org.apache.sshd.common.session.AbstractSession.doHandleMessage(AbstractSession.java:425)
  at org.apache.sshd.common.session.AbstractSession.handleMessage(AbstractSession.java:326)
  at org.apache.sshd.client.session.ClientSessionImpl.handleMessage(ClientSessionImpl.java:306)
  at org.apache.sshd.common.session.AbstractSession.decode(AbstractSession.java:780)
  at org.apache.sshd.common.session.AbstractSession.messageReceived(AbstractSession.java:308)
  at org.apache.sshd.common.AbstractSessionIoHandler.messageReceived(AbstractSessionIoHandler.java:54)
  at org.apache.sshd.common.io.nio2.Nio2Session$1.onCompleted(Nio2Session.java:184)
  at org.apache.sshd.common.io.nio2.Nio2Session$1.onCompleted(Nio2Session.java:170)
  at org.apache.sshd.common.io.nio2.Nio2CompletionHandler$1.run(Nio2CompletionHandler.java:32)
  at java.security.AccessController.doPrivileged(Native Method)
  at org.apache.sshd.common.io.nio2.Nio2CompletionHandler.completed(Nio2CompletionHandler.java:30)
  at sun.nio.ch.Invoker.invokeUnchecked(Unknown Source)
  at sun.nio.ch.Invoker$2.run(Unknown Source)
  at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  at java.lang.Thread.run(Unknown Source)
[                      Thread-3] Application           INFO  Application has been stopped

After some investigation and googling I found out that bouncyCastle jar is signed and cannot be put inside the fat jar.

To make it work the bouncyCastle jar should be in classpath, but not in the fat jar.

Solution 1 - add JCE provider to the classpath

The idea is following:

  • copy the signed jar into some folder
  • exclude this jar from the fat jar
  • add this jar to the manifest file as classpath.

This can be easily achieved with Maven Dependency Plugin and Maven Shade Plugin.

Below is an example of the plugin section of the pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.0.1</version>
    <executions>
        <execution>
            <id>copy-installed</id>
            <phase>package</phase>
            <goals>
                <goal>copy</goal>
            </goals>
            <configuration>
                <artifactItems>
                    <artifactItem>
                        <groupId>org.bouncycastle</groupId>
                        <artifactId>bcprov-jdk15on</artifactId>
                        <version>${bouncycastle-version}</version>
                        <type>jar</type>
                    </artifactItem>
                </artifactItems>
                <outputDirectory>${project.build.directory}/jce-jars</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.3</version>
    <configuration>
        <artifactSet>
            <excludes>
                <exclude>org.bouncycastle:*:*:*</exclude>
            </excludes>
        </artifactSet>
        <transformers>
            <transformer
                    implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <manifestEntries>
                    <Main-Class>com.application.Application</Main-Class>
                    <Class-Path>. ./jce-jars/bcprov-jdk15on-1.58.jar</Class-Path>
                </manifestEntries>
            </transformer>
        </transformers>
        <shadedArtifactAttached>true</shadedArtifactAttached>
        <shadedClassifierName>fat</shadedClassifierName>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
        </execution>
    </executions>
</plugin>

To run the app you need to have the fat jar and jce-jars folder with bcprov-jdk15on-1.58.jar in it as it’s expected by the MANIFEST.MF.

Solution 2 - register bouncyCastle

This one is much simpler. Download the bcprov jar from official site: bouncycastle.org/latest_releases and put it under $JAVA_HOME/jre/lib/ext/. And then add it as the last option in $JAVA_HOME/jre/lib/security/java.security, in my case it looks like following:

java.security

...
security.provider.7=com.sun.security.sasl.Provider
security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI
security.provider.9=sun.security.smartcardio.SunPCSC
security.provider.10=org.bouncycastle.jce.provider.BouncyCastleProvider

More info could be found in Oracle docs.