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)

Reply via email to