2015-09-04 16 views
8

Ambiente: sto utilizzando Sun Java JDK 1.8.0_60 su Windows 7 a 64 bit, utilizzando Spring Integration 4.1.6 (che internamente sembra utilizzare Apache Commons Net 3.3 per l'accesso FTPS).Come connettersi al server FTPS con connessione dati utilizzando la stessa sessione TLS?

Sto tentando di integrare con la nostra applicazione un download automatico dal server FTPS del nostro cliente. Ho eseguito così con successo con i server SFTP usando Spring Integration senza problemi per gli altri client senza problemi, ma questa è la prima volta che un client ci richiede di usare FTPS, e collegarlo è stato molto sconcertante. Mentre nella mia reale applicazione sto configurando Spring Integration usando i bean XML, per cercare di capire cosa non funziona sto usando il seguente codice di test (anche se sto anonimizzando l'host/username/password qui):

final DefaultFtpsSessionFactory sessionFactory = new DefaultFtpsSessionFactory(); 
sessionFactory.setHost("XXXXXXXXX"); 
sessionFactory.setPort(990); 
sessionFactory.setUsername("XXXXXXX"); 
sessionFactory.setPassword("XXXXXXX"); 
sessionFactory.setClientMode(2); 
sessionFactory.setFileType(2); 
sessionFactory.setUseClientMode(true); 
sessionFactory.setImplicit(true); 
sessionFactory.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager()); 
sessionFactory.setProt("P"); 
sessionFactory.setProtocol("TLSv1.2"); 
sessionFactory.setProtocols(new String[]{"TLSv1.2"}); 
sessionFactory.setSessionCreation(true); 
sessionFactory.setCipherSuites(new String[]{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}); 

final FtpSession session = sessionFactory.getSession(); 
//try { 
    final FTPFile[] ftpFiles = session.list("/"); 
    logger.debug("FtpFiles: {}", (Object[]) ftpFiles); 
//} catch (Exception ignored) {} 
session.close(); 

Sto eseguendo questo codice con -Djavax.net.debug=all per stampare tutte le informazioni di debug di TLS.

La connessione "di controllo" principale al server FTPS funziona correttamente, ma quando tenta di aprire la connessione dati per l'elenco (o qualsiasi altra connessione dati che ho provato), ottengo un javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake, causato da java.io.EOFException: SSL peer shut down incorrectly. Se io rimuovere il commento le deglutizione-eccezioni cattura blocco attorno al comando session.list, allora posso vedere (anche se l'output javax.net.debug) che il server ha inviato il seguente messaggio dopo aver respinto la connessione dati handshake SSL:

main, READ: TLSv1.2 Application Data, length = 129 
Padded plaintext after DECRYPTION: len = 105 
0000: 34 35 30 20 54 4C 53 20 73 65 73 73 69 6F 6E 20 450 TLS session 
0010: 6F 66 20 64 61 74 61 20 63 6F 6E 6E 65 63 74 69 of data connecti 
0020: 6F 6E 20 68 61 73 20 6E 6F 74 20 72 65 73 75 6D on has not resum 
0030: 65 64 20 6F 72 20 74 68 65 20 73 65 73 73 69 6F ed or the sessio 
0040: 6E 20 64 6F 65 73 20 6E 6F 74 20 6D 61 74 63 68 n does not match 
0050: 20 74 68 65 20 63 6F 6E 74 72 6F 6C 20 63 6F 6E the control con 
0060: 6E 65 63 74 69 6F 6E 0D 0A      nection.. 

Quello che sembra stia accadendo (e questa è la mia prima volta che mi occupo di FTPS, anche se ho già avuto a che fare con FTP semplice) è che il modo in cui il server garantisce l'autenticazione e la crittografia sia sul controllo che sulle connessioni dati è che dopo un "normale" Connessione TLS per stabilire la connessione di controllo e l'autenticazione avviene lì, ogni connessione dati richiede al client di connettersi con la stessa sessione TLS. Questo ha senso per me come dovrebbe essere il lavoro, ma l'implementazione FTPS di Net Commons Apache non sembra farlo. Sembra che stia tentando di stabilire una nuova sessione TLS e quindi il server sta respingendo il tentativo.

In base a this question about resuming SSL sessions in JSSE, sembra che Java assuma o richieda una sessione diversa per ciascuna combinazione host/post. La mia ipotesi è che, poiché la connessione dati FTPS è su una porta diversa dalla connessione di controllo, non sta trovando la sessione esistente e sta tentando di stabilirne una nuova, quindi la connessione fallisce.

vedo tre possibilità principali:

  1. Il server non è seguendo lo standard FTPS nel richiedere la stessa sessione TLS sulla porta dati sulla porta di controllo. Posso connettermi al server (usando lo stesso host/utente/password che sto cercando di usare nel mio codice) usando FileZilla 3.13.1. Il server si identifica come "FileZilla Server 0.9.53 beta" al momento dell'accesso, quindi forse questa è una sorta di metodo proprietario di FileZilla per fare le cose, e c'è qualcosa di strano che devo fare per convincere Java ad usare la stessa sessione TLS.
  2. Il client Net Commons Apache in realtà non segue lo standard FTPS e consente solo un sottoinsieme che non consente di proteggere le connessioni dati. Ciò sembrerebbe strano, in quanto sembra essere il modo standard per connettersi a FTPS da Java.
  3. Mi manca completamente qualcosa e una diagnosi errata.

Apprezzerei qualsiasi direzione che è possibile fornire su come connettersi a questo tipo di server FTPS. Grazie.

+0

Fare riferimento a questo collegamento per i dettagli https://stackoverflow.com/questions/46631315/when-using-java-apache-ftpclient-for-ftp-tls-getting-remote-host-closed -connect/48616779 # 48616779 –

risposta

13

Infatti alcuni server FTP (S) richiedono che la sessione TLS/SSL venga riutilizzata per la connessione dati. Questa è una misura di sicurezza grazie alla quale il server può verificare che la connessione dati sia utilizzata dallo stesso client della connessione di controllo.

Alcuni riferimenti per i server FTP comuni:


Cosa può aiutare con l'implementazione è tha t Cyberduck FTP (S) client esegue il supporto TLS/SSL sessione di riutilizzo e utilizza Apache Commons libreria netto:

  • https://trac.cyberduck.io/ticket/5087 - chiave riutilizzo di sessione sulla connessione dati

  • Vedere il suo codice FTPClient.java (si estende Commons Net FTPSClient), in particolare la sua override of _prepareDataSocket_ method:

    @Override 
    protected void _prepareDataSocket_(final Socket socket) throws IOException { 
        if(preferences.getBoolean("ftp.tls.session.requirereuse")) { 
         if(socket instanceof SSLSocket) { 
          // Control socket is SSL 
          final SSLSession session = ((SSLSocket) _socket_).getSession(); 
          if(session.isValid()) { 
           final SSLSessionContext context = session.getSessionContext(); 
           context.setSessionCacheSize(preferences.getInteger("ftp.ssl.session.cache.size")); 
           try { 
            final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache"); 
            sessionHostPortCache.setAccessible(true); 
            final Object cache = sessionHostPortCache.get(context); 
            final Method method = cache.getClass().getDeclaredMethod("put", Object.class, Object.class); 
            method.setAccessible(true); 
            method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostName(), 
              String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session); 
            method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostAddress(), 
              String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session); 
           } 
           catch(NoSuchFieldException e) { 
            // Not running in expected JRE 
            log.warn("No field sessionHostPortCache in SSLSessionContext", e); 
           } 
           catch(Exception e) { 
            // Not running in expected JRE 
            log.warn(e.getMessage()); 
           } 
          } 
          else { 
           log.warn(String.format("SSL session %s for socket %s is not rejoinable", session, socket)); 
          } 
         } 
        } 
    } 
    
  • Sembra che il metodo _prepareDataSocket_ è stato aggiunto al Commons Net FTPSClient specificamente per consentire l'implementazione di TLS/SSL sessione di riuso:
    https://issues.apache.org/jira/browse/NET-426

    Un supporto nativo per il riutilizzo è ancora in sospeso:
    https://issues.apache.org/jira/browse/NET-408

  • Ovviamente sarà necessario sostituire l'integrazione Spring DefaultFtpsSessionFactory.createClientInstance() per restituire l'implementazione personalizzata FTPSClient con il supporto per il riutilizzo della sessione.

+0

Grazie mille. Stavo guardando attraverso l'Apache Commons Net JIRA, ma a quanto pare non ho scavato più lontano di te. E il codice di esempio di un progetto funzionante sembra funzionare perfettamente –

+0

Sei il benvenuto. In realtà avevo [lo stesso problema] (http://stackoverflow.com/q/7786352/850848) in passato (solo in C++/OpenSSL, non lo faccio Java), quindi sapevo cosa cercare su google. –

+0

Non so perché, ma questo funziona con la versione openjdk "1.8.0_151" e non funziona con la versione openjdk "1.8.0_161" –

2

Per far funzionare il suggerimento di Martin Prikryl per me che ho dovuto memorizzare la chiave non solo sotto socket.getInetAddress().getHostName() ma anche sotto socket.getInetAddress().getHostAddress(). (Soluzione rubato da here.)

0

È possibile utilizzare questa classe SSLSessionReuseFTPSClient:

import java.io.IOException; 
import java.lang.reflect.Field; 
import java.lang.reflect.Method; 
import java.net.Socket; 
import java.util.Locale; 

import javax.net.ssl.SSLSession; 
import javax.net.ssl.SSLSessionContext; 
import javax.net.ssl.SSLSocket; 

import org.apache.commons.net.ftp.FTPSClient; 

public class SSLSessionReuseFTPSClient extends FTPSClient { 

    // adapted from: 
    // https://trac.cyberduck.io/browser/trunk/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java 
    @Override 
    protected void _prepareDataSocket_(final Socket socket) throws IOException { 
     if (socket instanceof SSLSocket) { 
      // Control socket is SSL 
      final SSLSession session = ((SSLSocket) _socket_).getSession(); 
      if (session.isValid()) { 
       final SSLSessionContext context = session.getSessionContext(); 
       try { 
        final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache"); 
        sessionHostPortCache.setAccessible(true); 
        final Object cache = sessionHostPortCache.get(context); 
        final Method method = cache.getClass().getDeclaredMethod("put", Object.class, Object.class); 
        method.setAccessible(true); 
        method.invoke(cache, String 
          .format("%s:%s", socket.getInetAddress().getHostName(), String.valueOf(socket.getPort())) 
          .toLowerCase(Locale.ROOT), session); 
        method.invoke(cache, String 
          .format("%s:%s", socket.getInetAddress().getHostAddress(), String.valueOf(socket.getPort())) 
          .toLowerCase(Locale.ROOT), session); 
       } catch (NoSuchFieldException e) { 
        throw new IOException(e); 
       } catch (Exception e) { 
        throw new IOException(e); 
       } 
      } else { 
       throw new IOException("Invalid SSL Session"); 
      } 
     } 
    } 
} 

E con OpenJDK 1.8.0_161:

Dobbiamo impostare:

System.setProperty("jdk.tls.useExtendedMasterSecret", "false"); 

in base alle http://www.oracle.com/technetwork/java/javase/8u161-relnotes-4021379.html

Aggiunto hash di sessione TLS e supporto estensione master master esteso

In caso di problemi di compatibilità, un'applicazione può disabilitare la negoziazione di questa estensione impostando la Proprietà di sistema jdk.tls.useExtendedMasterSecret to false in JDK

Problemi correlati