bc-java
bc-java copied to clipboard
Session reuse with different port
We have a problem with our FTP client implementation and TLS session resumption required by some servers, for example FileZilla server that has it enabled by default. This option requires the client to use the same TLS session in the data session and control session. That means different ports for two connections and same TLS session.
For more information you can look up the common message "TLS session of data connection has not resumed or the session does not match the control connection".
We tried Oracle Java at first and found a workaround. But the workaround does not work with IBM i, were our client will run. Now we tried BouncyCastle as a SSLContext provider. Unfortunately the feature is not supported either.
Will TLS session reuse on different ports be supported?
We can look at adding support. Do you have an idea about how the API should work to access this feature? Note that BCJSSE does provide an extended API so you can contemplate features that aren't in the JSSE API itself, although relying on them of course creates a dependency.
Please also describe the workaround that you are using with the Oracle JRE.
We use the Apache commons net FTPSClient to connect to a FileZilla server. The workaround for Oracle JRE uses the method „prepareDataSocket“ (FTPSClient) that was implemented by Apache to make this workaround possible. Code example can be found at https://stackoverflow.com/questions/51405161/ftps-connection-is-throwing-an-error-in-android Oracle changed something, so this workaround is not possible anymore. I found a java property that can be set to make it work again (untested on my side). But let me just describe what it does:
It takes the SSLSessionContext from the current SSLSession. Then it adds the current key (host and port) to the sessionHostPortCache. Yes, that is everything. Using BouncyCastle I get the ProvSSLSessionContext that implements SSLSessionContext. Seems like you use hostName and port, too.
For an API idea:
I only use BouncyCastle once and that is on provider registration or calling SSLContext.getInstance with the specific provider. Example:
SSLContext clientContext = SSLContext.getInstance("TLS", new BouncyCastleJsseProvider());
So my idea is to add the option bindSessionToPort(boolean) to the provider. Example use:
BouncyCastleJsseProvider provider = new BouncyCastleJsseProvider(); provider.bindSessionToPort(false); SSLContext clientContext = SSLContext.getInstance("TLS", provider);
Is this issue being worked on, the possibilities discussed or is it paused?
Hi @Stefan4112 have you been able to handle this? If so, can you please share your findings?
I still use the reflection workaround in standard Java. I have no idea how to implement it with BouncyCastle myself.
The current workaround in Java can be found here. It is possible that it changes with each Java release.
For situations where the workaround is not possible I do a OpenSSL command call.
ons where the workaround is not possible I
Java 17 is not allowing reflection any more, "module java.base does not "opens sun.security.ssl" to unnamed module"
And ClientHello depends on the host/port
Trying out different approaches, but without any luck so far :)
If you are happy to use the BCJSSE extension API, then for the first connection collect the session like so (after connected):
BCExtendedSSLSession sessionToResume = ((BCSSLSocket)sslSocket1).getBCSession();
Then when initialising the second connection, you can configure it to reuse this exact session like so (before connecting):
((BCSSLSocket)sslSocket2).setBCSessionToResume(sessionToResume);
The same methods exist also for BCSSLEngine.
Correct me if I'm wrong but the last suggestion (with setBCSessionToResume) works for TLS 1.2 only. Is there a solution for TLS 1.3? I can hardly find an information on whether bctls even supports session reuse for 1.3 at all.
Hi,
Found this thread after attempting the solution outlined in this Stack Overflow Answer. Like OP, I am using Apache Commons Net FTPSClient and trying to connect to a FTP server that requires session reuse
I cannot get this to work. Here's my implementation (Note, I assure you I'm calling Security.addProvider(new BouncyCastleJsseProvider()) and SSLContext.getInstance("TLSv1.2", "BCJSSE") and initializing the context similar to the example in the SO Answer, I'm just doing it in my Dagger module) :
import org.apache.commons.net.ftp.FTPSClient
import org.bouncycastle.jsse.BCSSLSocket
import java.io.IOException
import java.net.Socket
import javax.net.ssl.SSLContext
class BouncyCastleFTPSClient(
isImplicit: Boolean,
sslContext: SSLContext,
) : FTPSClient(isImplicit, sslContext) {
@Throws(IOException::class)
override fun _connectAction_() {
super._connectAction_()
setEnabledSessionCreation(false)
}
override fun _prepareDataSocket_(dataSocket: Socket?) {
val controlSocket = _socket_ as BCSSLSocket?
if (controlSocket is BCSSLSocket && dataSocket is BCSSLSocket) {
val bcSession = controlSocket.bcSession
if (bcSession != null && bcSession.isValid) {
dataSocket.setBCSessionToResume(bcSession)
}
}
}
}
When I have setEnabledSessionCreation(false), I get the exception java.lang.IllegalStateException: Cannot resume session and session creation is disabled. When I set it to true, I get the error response from the server Reply Received: 522 522 SSL connection failed: session reuse required. So from what I can tell, even though I'm setting dataSocket to use the same session as controlSocket it's not actually reusing the session, it's creating a new one (or attempting to and failing if I call setEnabledSessionCreation(false).
If I had to guess why it's because the ports are different between the controlSocket and the dataSocket, and this is why it's not being reused. And this is why @Stefan4112 was asking years ago for the ability to configure the provider to not bind the port to the session, so we can use the session with any port.
Is there a way to actually achieve this? My only other solution is to use some ugly reflection I don't understand from SO, and worse, since I am on Android I have to use a library to break a blacklist Google sets on using these APIs reflectively, which might get me rejected from the store. So using BouncyCastle seems like the only "legal" way to solve this problem on Android.
BC doesn't restrict the session to the same port or even host, it just tries to use the one you specify in setBCSessionToResume.
I would guess that for some reason the session is considered unresumable. Could you check the runtime type of 'bcSession' in your example? (Before a handshake has completed a temporary "handshake session" is active and I don't think that is resumable).
Also, could you give the stack trace for the exception with message "Cannot resume session and session creation is disabled". I see two call sites, one would mean the local client thinks it's unresumable, the other means the remote server declined to resume it.
Hi,
As requested, here's the full stack trace that occurs when I have setEnabledSessionCreation(false):
java.lang.IllegalStateException: Cannot resume session and session creation is disabled
at org.bouncycastle.jsse.provider.JsseUtils.checkSessionCreationEnabled(Unknown Source:11)
at org.bouncycastle.jsse.provider.ProvTlsClient.notifySessionToResume(Unknown Source:4)
at org.bouncycastle.tls.TlsClientProtocol.sendClientHello(Unknown Source:239)
at org.bouncycastle.tls.TlsClientProtocol.beginHandshake(Unknown Source:3)
at org.bouncycastle.tls.TlsClientProtocol.connect(Unknown Source:28)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown Source:37)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown Source:2)
at org.apache.commons.net.ftp.FTPSClient._openDataConnection_(FTPSClient.java:300)
at org.apache.commons.net.ftp.FTPClient._openDataConnection_(FTPClient.java:664)
at org.apache.commons.net.ftp.FTPClient.initiateListParsing(FTPClient.java:1985)
at org.apache.commons.net.ftp.FTPClient.initiateListParsing(FTPClient.java:2082)
at org.apache.commons.net.ftp.FTPClient.listFiles(FTPClient.java:2282)
at org.apache.commons.net.ftp.FTPClient.listFiles(FTPClient.java:2247)
at com.dptsolutions.landy.ftp.PrinterFtpClient$listFiles$2.invokeSuspend(PrinterFtpClient.kt:127)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
As for the runtime type of bcSession, it appears to be of type ProvSSLSession:
Please let me know if there's anything else I can provide. I'm just out of my depth at this point.
Thanks for the stack trace. It seems clear that the low-level TLS code (TlsClientProtocol.sendClientHello) first calls ProvTlsClient.getSessionToResume to get the session and almost certainly in your case the intended session is being returned (but it is provisional at this stage). Then later in the sendClientHello method ProvTlsClient.notifySessionToResume is called to inform of the session that will definitely be used; based on your stack trace, the session is now null.
So somewhere in https://github.com/bcgit/bc-java/blob/2f4d33d57797dcc3fe9bd4ecb07ee0557ff58185/tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java#L1790-L1847 the session is rejected for some reason.
Are you able to get the bctls-debug jar and debug into that code to find out what happens? The main points to note are:
- whether the call to establishSession actually sets this.tlsSession (to non-null) and returns true
- is cancelSession called near the end of that section? If so, where was legacy_session_id set to EMPTY_BYTES?
Having said that, my guess is that the original session is not using extended_master_secret (EMS) and the default rule is only to resume sessions that use EMS. So you might just try setting the system property "jdk.tls.allowLegacyResumption" to true and see if it allows things to work.
Having said that, my guess is that the original session is not using extended_master_secret (EMS) and the default rule is only to resume sessions that use EMS. So you might just try setting the system property "jdk.tls.allowLegacyResumption" to true and see if it allows things to work.
SUCCESS! Setting that property did the trick. Thank you so much for your help!
FWIW - I can do the debugging if you still think it would be helpful in some way. It'll just take me a bit of time to set up.