2013-06-24 20 views
2

嗨,下面的代碼允許將字節通道升級到SSL。如何將Java nio SSLBytechannel更改爲ByteChannel

有誰知道如何恢復過程?我希望能夠升級或降級Java nio字節通道,或更改通道而不關閉套接字。

現在寫下使用下面的代碼來升級字節通道。我請你專業來創造一個相反的功能。請。

//調用

ByteChannel sslbytechannel = upgradeChannel2ServerSSLChannel(sourcebytechannel); 


//function 

    private ByteChannel upgradeChannel2ServerSSLChannel(ByteChannel channel) { 
     try { 
      if (log.isLoggable(Level.FINE)) { 
       log.fine("Switching socket to SSL"); 
      } 

      KeyStore ks = KeyStore.getInstance("JKS"); 
      File kf = new File(getExproxy().getKeystoreFilename()); 
      ks.load(new FileInputStream(kf), getExproxy().getKeystorePassword()); 

      KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 
      kmf.init(ks, getExproxy().getKeystoreKeysPassword()); 

      TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); 
      tmf.init(ks); 

      SSLContext sslContext = SSLContext.getInstance("TLS"); 
      sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); 

      SSLEngine engine = sslContext.createSSLEngine(); 
      engine.setUseClientMode(false); 
      engine.beginHandshake(); 

      return new SSLByteChannel(channel, engine); 
     } catch(Exception e) { 
      log.log(Level.SEVERE, "Exception during server SSL channel upgrade", e); 
     } 
     return null; 
    } 

//Class 

    import java.io.IOException; 
    import java.nio.ByteBuffer; 
    import java.nio.channels.ByteChannel; 
    import java.nio.channels.ClosedChannelException; 
    import java.util.logging.Level; 
    import java.util.logging.Logger; 
    import javax.net.ssl.SSLEngine; 
    import javax.net.ssl.SSLEngineResult; 
    import javax.net.ssl.SSLException; 
    import java 

x.net.ssl.SSLSession; 

/** 
* Upgrade a ByteChannel for SSL. 
* 
* <p> 
* Change Log: 
* </p> 
* <ul> 
* <li>v1.0.1 - Dead lock bug fix, take into account EOF during read and unwrap.</li> 
* <li>v1.0.0 - First public release.</li> 
* </ul> 
* 
* <p> 
* This source code is given to the Public Domain. Do what you want with it. 
* This software comes with no guarantees or warranties. 
* Please visit <a href="http://perso.wanadoo.fr/reuse/sslbytechannel/">http://perso.wanadoo.fr/reuse/sslbytechannel/</a> 
* periodically to check for updates or to contribute improvements. 
* </p> 
* 
* @author David Crosson 
* @author [email protected] 
* @version 1.0.0 
*/ 
public class SSLByteChannel implements ByteChannel { 
    private ByteChannel wrappedChannel; 
    private boolean closed = false; 
    private SSLEngine engine; 

    private final ByteBuffer inAppData; 
    private final ByteBuffer outAppData; 

    private final ByteBuffer inNetData; 
    private final ByteBuffer outNetData; 

    private final Logger log = Logger.getLogger(getClass().getName()); 


    /** 
    * Creates a new instance of SSLByteChannel 
    * @param wrappedChannel The byte channel on which this ssl channel is built. 
    * This channel contains encrypted data. 
    * @param engine A SSLEngine instance that will remember SSL current 
    * context. Warning, such an instance CAN NOT be shared 
    * between multiple SSLByteChannel. 
    */ 
    public SSLByteChannel(ByteChannel wrappedChannel, SSLEngine engine) { 
     this.wrappedChannel = wrappedChannel; 
     this.engine = engine; 

     SSLSession session = engine.getSession(); 
     inAppData = ByteBuffer.allocate(session.getApplicationBufferSize()); 
     outAppData = ByteBuffer.allocate(session.getApplicationBufferSize()); 

     inNetData = ByteBuffer.allocate(session.getPacketBufferSize()); 
     outNetData = ByteBuffer.allocate(session.getPacketBufferSize()); 
    } 


    /** 
    * Ends SSL operation and close the wrapped byte channel 
    * @throws java.io.IOException May be raised by close operation on wrapped byte channel 
    */ 
    public void close() throws java.io.IOException { 
     if (!closed) { 
      try { 
       engine.closeOutbound(); 
       sslLoop(wrap()); 
       wrappedChannel.close(); 
      } finally { 
       closed=true; 
      } 
     } 
    } 


    public SSLByteChannel(ByteChannel wrappedChannel) { 
     this.wrappedChannel = null; 
     this.engine = null; 


     inAppData = ByteBuffer.allocate(4096); 
     outAppData = ByteBuffer.allocate(4096); 

     inNetData = ByteBuffer.allocate(4096); 
     outNetData = ByteBuffer.allocate(4096); 
    } 



    /** 
    * Is the channel open ? 
    * @return true if the channel is still open 
    */ 
    public boolean isOpen() { 
     return !closed; 
    } 

    /** 
    * Fill the given buffer with some bytes and return the number of bytes 
    * added in the buffer.<br> 
    * This method may return immediately with nothing added in the buffer. 
    * This method must be use exactly in the same way of ByteChannel read 
    * operation, so be careful with buffer position, limit, ... Check 
    * corresponding javadoc. 
    * @param byteBuffer The buffer that will received read bytes 
    * @throws java.io.IOException May be raised by ByteChannel read operation 
    * @return The number of bytes read 
    */ 
    public int read(java.nio.ByteBuffer byteBuffer) throws java.io.IOException { 
     boolean eofDuringUnwrap = false; 
     if (isOpen()) { 
      try { 
       SSLEngineResult r = sslLoop(unwrap()); 
       if (r==null) eofDuringUnwrap = true; 
      } catch(SSLException e) { 
       log.log(Level.SEVERE, "SSLException while reading", e);// TODO : Better SSL Exception management must be done 
      } catch(ClosedChannelException e) { 
       close(); 
      } 
     } 

     inAppData.flip(); 
     int posBefore = inAppData.position(); 
     byteBuffer.put(inAppData); 
     int posAfter = inAppData.position(); 
     inAppData.compact(); 

     if (posAfter - posBefore > 0) return posAfter - posBefore ; 
     if (isOpen()) 
      return (eofDuringUnwrap)?-1:0; 
     else 
      return -1; 
    } 

    /** 
    * Write remaining bytes of the given byte buffer. 
    * This method may return immediately with nothing written. 
    * This method must be use exactly in the same way of ByteChannel write 
    * operation, so be careful with buffer position, limit, ... Check 
    * corresponding javadoc. 
    * @param byteBuffer buffer with remaining bytes to write 
    * @throws java.io.IOException May be raised by ByteChannel write operation 
    * @return The number of bytes written 
    */ 
    public int write(java.nio.ByteBuffer byteBuffer) throws java.io.IOException { 
     if (!isOpen()) return 0; 
     int posBefore, posAfter; 

     posBefore = byteBuffer.position(); 
     if (byteBuffer.remaining() < outAppData.remaining()) { 
      outAppData.put(byteBuffer); // throw a BufferOverflowException if byteBuffer.remaining() > outAppData.remaining() 
     } else { 
      while (byteBuffer.hasRemaining() && outAppData.hasRemaining()) { 
      outAppData.put(byteBuffer.get()); 
      } 
     } 
     posAfter = byteBuffer.position(); 

     if (isOpen()) { 
      try { 
       while(true) { 
        SSLEngineResult r = sslLoop(wrap()); 
        if (r.bytesConsumed() == 0 && r.bytesProduced()==0) break; 
       }; 
      } catch(SSLException e) { 
       log.log(Level.SEVERE, "SSLException while reading", e); // TODO : Better SSL Exception management must be done 
      } catch(ClosedChannelException e) { 
       close(); 
      } 
     } 

     return posAfter - posBefore; 
    } 


    public void writeclean(java.nio.ByteBuffer byteBuffer) throws java.io.IOException { 


     if (isOpen()) { 
      try { 
       while(true) { 
        wrappedChannel.write(outAppData); 
       } 
      } catch(SSLException e) { 
       log.log(Level.SEVERE, "SSLException while reading", e); // TODO : Better SSL Exception management must be done 
      } catch(ClosedChannelException e) { 
       close(); 
      } 
     } 


    } 



    private SSLEngineResult unwrap() throws IOException, SSLException { 
     int l; 
     while((l = wrappedChannel.read(inNetData)) > 0) { 
      try { 
       Thread.sleep(10); // Small tempo as non blocking channel is used 
      } catch(InterruptedException e) { 
      } 
     } 

     inNetData.flip(); 

     if (l==-1 && !inNetData.hasRemaining()) return null; 

     SSLEngineResult ser = engine.unwrap(inNetData, inAppData); 
     inNetData.compact(); 

     return ser; 
    } 

    private SSLEngineResult wrap() throws IOException, SSLException { 
     SSLEngineResult ser=null; 

     outAppData.flip(); 
     ser = engine.wrap(outAppData, outNetData); 
     outAppData.compact(); 

     outNetData.flip(); 
     while(outNetData.hasRemaining()) { 
      int l = wrappedChannel.write(outNetData); // TODO : To be enhanced (potential deadlock ?) 
      try { 
       Thread.sleep(10); // Small tempo as non blocking channel is used 
      } catch(InterruptedException e) { 
      } 
     } 
     outNetData.compact(); 

     return ser; 
    } 

    private SSLEngineResult sslLoop(SSLEngineResult ser) throws SSLException, IOException { 
     if (ser==null) return ser; 
     //log.finest(String.format("%s - %s\n", ser.getStatus().toString(), ser.getHandshakeStatus().toString())); 
    // System.out.println(String.format("%s - %s\n", ser.getStatus().toString(), ser.getHandshakeStatus().toString())); 
     while( ser.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.FINISHED 
       && ser.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { 
      switch(ser.getHandshakeStatus()) { 
       case NEED_TASK: 
        //Executor exec = Executors.newSingleThreadExecutor(); 
        Runnable task; 
        while ((task=engine.getDelegatedTask()) != null) { 
         //exec.execute(task); 
         task.run(); 
        } 
        // Must continue with wrap as data must be sent 
       case NEED_WRAP: 
        ser = wrap(); 
        break; 
       case NEED_UNWRAP: 
        ser = unwrap(); 
        break; 
      } 
      if (ser == null) return ser; 
     } 
     switch(ser.getStatus()) { 
      case CLOSED: 
       log.finest("SSLEngine operations finishes, closing the socket"); 
       try { 
        wrappedChannel.close(); 
       } finally { 
        closed=true; 
       } 
       break; 
     } 
     return ser; 
    } 

} 

回答

2

通過定期HTTPS請求,則無法啓動談論純文本,然後切換到SSL,然後再返回到純文本。您必須承諾純文本或SSL通信模式。

我能想到的允許將純文本升級到SSL的唯一真實生活實現是STARTTLS和ESMTP。但即使如此,一旦建立了SSL連接,您也無法將其降級回純文本。

因此,除非您正在推出自己的服務器協議,否則不需要進行SSL降級。

編輯
對於回落到未加密的通信

ByteChannel sslByteChannel = upgradeChannel2ServerSSLChannel(sourceByteChannel); 

try 
{ 
    doSslPortion(sslByteChannel); 

    doPlainPortion(sourceByteChannel); 
} 
finally 
{ 
    sourceByteChannel.close(); 
} 
+0

喜亞歷山大僞代碼,感謝您使用行爲類似這樣的客戶端應用程序在這種情況下即時回覆。這個客戶端連接到多個目的地,一些服務器是協議特定的,能夠說出ssl,但第一個回覆總是純文本。這意味着我正在集成的客戶端必須以ssl開頭,然後期望純文本回復。 – user2505009

+1

@ user2505009。您需要在系統中保留一份'sourcebytechannel',當您完成協議的SSL部分時,只需使用'sourcebytechannel'。這對讀取未加密的數據很有幫助。在讀取未加密的數據之前,請確保您不要關閉sslbytechannel。在讀取未加密的數據後,關閉'sourcebytechannel' –

+0

感謝Alexander,你可以發表一些代碼來展示它的工作原理嗎?我不知道你是什麼意思的副本,但在我的測試中,如果我創建像 ByteChannel克隆=(ByteChannel)源; 升級到SSL之前,結果始終是現有的對象。可能是我做錯了什麼?使用我的代碼可以讓你知道你的想法嗎? – user2505009