在我工作的地方,我们拥有庞大的服务器区域,可以使用使用FTP的内部软件传输文件。目前正在计划升级所有FTP以使用FTPS和SSL证书。
在撰写这个问题时,我正在开发一个项目,使用Java / Apache来实现文件传输软件的现代化(它已经很老了)。
我使用Apache软件编写了一个FTP客户端,并且还编写了一个非常类似的FTPS客户端。两者都按预期工作。
但是,如果FTPS客户端尝试连接到非FTPS服务器,则会抛出SSLException。 FTP客户端在这种情况下工作正常。
最终,我希望将两个客户合理化为一个可以管理FTP和客户端的客户端。 FTPS连接。
我的问题很简单:
在尝试SSL连接之前,是否有办法使用Apache / Java检测远程服务器上正在使用的协议?
更好的解决方案是模拟cURL并尝试FTPS并下拉到FTP。
非常感谢任何帮助或建议。
以下代码
package jtm.ftp.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLException;
import jtm.common.JtmConstants;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;
import org.apache.commons.net.util.TrustManagerUtils;
import org.apache.log4j.Logger;
public class JtmFTPSClient
{
static Logger log = Logger.getLogger( JtmFTPSClient.class.getName( ) );
private static int PORT = JtmConstants.FTP_PORT;
private static FTPSClient ftps = null;
private static boolean IS_IMPLICIT = false;
public static boolean getOrPut( String correlationID, String hostname, String user, String pass, String remoteFile, String localFile, String mode, String direction )
{
ftps = new FTPSClient( IS_IMPLICIT ); //look at using a constructor here perhaps - see the SWIPE FTPSClient
boolean ftpsSuccess = false;
log.debug( correlationID + " FTPS GET initiated" );
try
{
if( setSslConfig( ) == false )
{
return false;
}
String fileTermName = new File( remoteFile ).getName();
/*
* Configuration Section - source this from the FTPConfig object ** that needs to be made more flexible
*/
log.info( correlationID + " FTPS Server Address : " + hostname );
log.info( correlationID + " FTPS Port Number : " + PORT );
log.info( correlationID + " FTPS remoteFile : " + remoteFile );
log.info( correlationID + " FTPS localFile : " + localFile );
log.info( correlationID + " FTPS remoteTermName : " + fileTermName );
// try to connect
ftps.connect( hostname, PORT );
/*
* Once connected we need to login to the server via username/password
*/
if ( !ftps.login( user, pass ) )
{
log.error( correlationID + " FTPS Unable to log into : " + hostname + " with supplied credentials" );
return false;
}
int reply = ftps.getReplyCode();
log.debug( correlationID + " FTPS Login Reply Code : " + reply );
/*
* http://forus.com/csm/ftps/ trying to SSL the crap out of this client
*/
ftps.execPBSZ( 0 );
ftps.execPROT( "P" );
/*
* FTPReply stores a set of constants for FTP reply codes.
*/
if ( !FTPReply.isPositiveCompletion( reply ) )
{
log.error( correlationID + " FTPS Not a positive reply from " + hostname + " : " + reply);
return false;
}
log.info( correlationID + " FTPS Logged into to Remote Host : " + hostname );
ftps.enterLocalPassiveMode();
log.debug( correlationID + " FTPS Entered Local Passive Mode");
int xferMode = FTP.BINARY_FILE_TYPE;
if( mode.equalsIgnoreCase("b") ) {
xferMode = FTP.BINARY_FILE_TYPE;
}
else if( mode.equalsIgnoreCase("t")) {
xferMode = FTP.ASCII_FILE_TYPE;
}
else
{
xferMode = FTP.BINARY_FILE_TYPE;
}
log.debug(correlationID + " Mode for FTP : " + xferMode );
// Set the buffer size to cope with larger files
ftps.setBufferSize( 1024 * 1024 );
log.debug( correlationID + " FTPS Set Buffer Size to : " + ftps.getBufferSize( ) );
log.debug( correlationID + " FTPS Remote system type : " + ftps.getSystemType( ) );
log.debug( correlationID + " FTPS Remote directory is : " + ftps.printWorkingDirectory( ) );
log.info( correlationID + " FTPS Remote file is " + remoteFile );
// Get output stream - This is where the file will be downloaded to
if(direction.equalsIgnoreCase( "get" ) )
{
OutputStream downloadedFile = new FileOutputStream( localFile );
/*
* TODO Check that the remote file exists before download
*
* TODO Also a check that a local copy of the file does not already exist.
*/
/*
* GET the file from the remote system ( remoteFile, downloadedFile )
*/
ftpsSuccess = ftps.retrieveFile( remoteFile, downloadedFile );
/*
* close output stream
*/
downloadedFile.close();
log.info( correlationID + " FTPS Retrieval Complete for " + remoteFile );
}
else if( direction.equalsIgnoreCase("put"))
{
FileInputStream file = new FileInputStream( localFile );
ftpsSuccess = ftps.storeFile(remoteFile, file );
file.close();
}
if( ftpsSuccess == false )
{
log.error( correlationID + "FTPS Success is False" );
}
}
catch( SSLException e )
{
log.error("SSLException caught ", e );
/*
* Do we drop down to basic FTP here and try the transfer again?
*/
//return JtmFTPClient.get(correlationID, hostname, user, pass, remoteFile, localFile, mode);
ftpsSuccess = false;
}
catch ( FileNotFoundException e )
{
log.error( correlationID + " FileNotFoundException caught ", e );
ftpsSuccess = false;
}
catch ( GeneralSecurityException e )
{
log.error( correlationID + " GeneralSecurityException caught ", e );
ftpsSuccess = false;
}
catch ( IOException e )
{
log.error( correlationID + " IOException caught ", e );
ftpsSuccess = false;
}
finally
{
try
{
ftps.logout();
ftps.disconnect();
}
catch ( IOException e )
{
log.error(correlationID + " IOException caught closing", e );
}
}
log.debug(correlationID + " FTPS result : " + ftpsSuccess );
return ftpsSuccess;
}
/**
* A Method that configures the SSL requirements when FTP'ing files to/from secure instances of UTM
*
* @param isSSL
* @return boolean
* @throws IOException
* @throws GeneralSecurityException
*/
private static boolean setSslConfig( ) throws IOException, GeneralSecurityException
{
String trustStorePath = JtmConstants.TRUST_STORE_PATH;
String trustStorePass = JtmConstants.TRUST_STORE_PASS;
String keyStorePath = JtmConstants.KEY_STORE_PATH;
String keyStorePass = JtmConstants.KEY_STORE_PASS;
String keyPass = JtmConstants.KEY_PASS;
String keyAlias = JtmConstants.KEY_ALIAS;
boolean isSslRequired = true;
if ( isSslRequired )
{
if ( trustStorePath != null && trustStorePass != null )
{
KeyStore ks = KeyStore.getInstance( "JKS" );
ks.load( new FileInputStream( trustStorePath ), trustStorePass.toCharArray( ) );
ftps.setTrustManager( TrustManagerUtils.getDefaultTrustManager( ks ) );
}
else
{
log.error( "Error setting up Trust Store" );
log.error( "Trust Store path or trust store passord have not been supplied." );
return false;
}
}
else
{
ftps.setTrustManager( TrustManagerUtils.getAcceptAllTrustManager( ) );
}
if ( keyStorePath != null && keyStorePass != null )
{
File keyFile = new File( keyStorePath );
KeyManager keyManager;
if ( keyAlias != null )
{
if ( keyPass != null )
{
keyManager = org.apache.commons.net.util.KeyManagerUtils
.createClientKeyManager( "JKS", keyFile,
keyStorePass,
keyAlias,
keyPass );
}
else
{
keyManager = org.apache.commons.net.util.KeyManagerUtils
.createClientKeyManager( keyFile, keyStorePass, keyAlias );
}
}
else
{
keyManager = org.apache.commons.net.util.KeyManagerUtils
.createClientKeyManager( keyFile, keyStorePass );
}
ftps.setKeyManager( keyManager );
return true;
}
else
{
log.error( "Error setting up Key Store" );
log.error( "Key Store path or key store passord have not been supplied." );
return false;
}
}
}
答案 0 :(得分:3)
不要这样做。你只是假装安全。
如果您允许自动降级到不安全的连接,有很多方法可以被黑客入侵。至少有两个明显的方法:
攻击者只需将DNS查找重定向到恶意的不安全服务器,您就不会知道您刚刚丢失了凭据。
攻击者可以模拟AUTH
命令的失败(在连接获得安全之前发生)。您会自动降级到不安全的连接,再次以明文形式向攻击者显示您的凭据。
无论如何,只需尝试FTPSClient
,如果SSLException
失败,请使用FTPClient
。
如果你真的需要一个很好的解决方案(没有重新连接)用于显式TLS / SSL,请参阅FTPSClient._connectAction_()
的实现方式。您可以重新实现它以调用基础FTPClient._connectAction_()
并尝试sendCommand(CMD_AUTH, auth)
,而不会抛出。当然,如果sslNegotiation()
失败,请不要致电AUTH
。