Bruno Gonçalves created NET-744:
-----------------------------------
Summary: FTPS Passive Mode throws 425 Error due to missing TLS
session resumption
Key: NET-744
URL: https://issues.apache.org/jira/browse/NET-744
Project: Commons Net
Issue Type: Bug
Components: FTP
Affects Versions: 3.13.0
Reporter: Bruno Gonçalves
*Environment*
Java Version: JDK 21
FTPS Server: FileZilla Server 1.12.6
Protocol: FTPS
Library:
- Apache Camel 4.10.9/4.20.0
- Apache Commons Net (master + [PR
#395|https://github.com/apache/commons-net/pull/395])
*Problem Description*
When connecting to a strict FTPS server that mandates TLS session resumption on
the data channel (FileZilla Server 1.12.6), file transfers fail with the
following error:
{code:java}
425 Unable to build data connection: TLS session of data connection not
resumed{code}
*PR #395 Does Not Resolve This*
I pulled the latest master branch and tested the changes proposed in [PR
#395|https://github.com/apache/commons-net/pull/395], but the issue persists.
The method {{_parseExtendedPassiveModeReply}} is not invoked.
Added some logs on {{openDataSecureConnection}} method, and saw the following:
{code:java}
isUseEPSVwithIPv4: false
isInet6Address: false
attemptEPSV: false
epsv(): 229{code}
*Solution* (FTPSClient)
The following seems to work on my side, but it uses reflection and only works
with TLSv1.2:
{code:java}
protected void _prepareDataSocket_(final Socket socket) throws IOException {
System.out.println(" > _prepareDataSocket_");
if (socket instanceof SSLSocket) {
final SSLSocket dataSocket = (SSLSocket) socket;
if (_socket_ instanceof SSLSocket) {
final SSLSession controlSession = ((SSLSocket) _socket_).getSession();
if (controlSession != null) {
try {
// Extract the exact host identity used for the control line
String controlHost = controlSession.getPeerHost();
// 1. Force override the peerHost string on the socket engine
try {
Class<?> sslSocketImplClass = Class.forName("sun.security.ssl.SSLSocketImpl");
if (sslSocketImplClass.isInstance(dataSocket)) {
java.lang.reflect.Field peerHostField =
sslSocketImplClass.getDeclaredField("peerHost");
peerHostField.setAccessible(true);
peerHostField.set(dataSocket, controlHost);
}
} catch (Exception e) {
// If peerHost field name changes, pass through safely
}
// 2. Clear out Endpoint Identification boundaries to prevent host mismatches
javax.net.ssl.SSLParameters sslParams = dataSocket.getSSLParameters();
sslParams.setEndpointIdentificationAlgorithm(null);
dataSocket.setSSLParameters(sslParams);
// 3. Re-seed the session context cache with the exact address this socket is
using
final javax.net.ssl.SSLSessionContext context =
controlSession.getSessionContext();
Class<?> cacheBaseClass = Class.forName("sun.security.util.Cache");
java.lang.reflect.Field cacheField =
context.getClass().getDeclaredField("sessionHostPortCache");
cacheField.setAccessible(true);
Object cache = cacheField.get(context);
if (cache != null) {
java.lang.reflect.Method putMethod = cacheBaseClass.getDeclaredMethod("put",
Object.class, Object.class);
putMethod.setAccessible(true);
// Map the session to the current data socket's destination string combo
int dataPort = dataSocket.getPort();
String dataIp = dataSocket.getInetAddress().getHostAddress().toLowerCase();
putMethod.invoke(cache, dataIp + ":" + dataPort, controlSession);
System.out.println(" [Commons-Net Patch] Alignment and session seeding
complete.");
}
} catch (final Exception e) {
System.err.println("[Commons-Net Patch] Optimization bypassed: " +
e.getMessage());
}
}
}
}
}{code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)