sslengine


https://blog.csdn.net/www646288178/article/details/112218359

 

 

1、TLSv1.2 Handshake步驟:

在java8 JSSE中,TLSv1.2的handshake文檔鏈接:https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/tls.html#the_tls_1.2_handshake

下面我們直接上一個握手過程圖:

This figure shows the sequence of messages that are exchanged in the SSL handshake. These messages are described in detail in the following text.

在TLSv1.2 handshake中,排除掉Optional的選項(這里注意:證書部分也是可選內容,也就是說HTTPS這些基於SSL/TLS協議的通信,其實是不需要構建證書也可以訪問的,前提是密匙交換算法要支持無證書模式),只看Handshake的必經階段,這里摘自oracle官方SSLEngine Demo的內容:

鏈接:https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/samples/sslengine/SSLEngineSimpleDemo.java

可以看到,整個handshake步驟如下:

  1. client端發送clientHello packet,其中包含了client端所支持的TLS協議版本列表、cipher suites列表等。(wrap階段)
  2. server端獲取到clientHello后,進行協議版本和密匙交換算法等內容的協商。(unwrap階段)
  3. server將第2步的協商結果通過serverHello packet返回給client。(wrap階段)
  4. client端獲取serverHello的協商結果。(unwrap階段)。
  5. client發送clientKeyExchange數據包,其中包含用於生成通信密匙的public key。(wrap階段)
  6. client發送ChangeCipherSpec數據包,通知server端,我client已經准備好進行加密通信了.(wrap階段)
  7. client發送Finished數據包。(wrap階段)
  8. server端獲取到用於生成通信密匙的client public key。(unwrap階段)
  9. server端獲取到client端准備好加密通信的通知。(unwrap階段)
  10. server端獲取到client的Finished packet。(unwrap階段)
  11. server端發送ChangeCipherSpec,通知client端我server端也准備好進行加密通信了。(wrap階段)
  12. server端發送Finished。(wrap階段),到此Server端的TLS handshake完成。
  13. client端獲取到server的ChangeCipherSpec和Finished。到此Client端的TLS handshake完成。

附上一段密匙交換算法的文章:https://www.zhihu.com/question/65464646/answer/231934108

2、TLSv1.2 shutdown步驟

TLSv1.2的shutdown過程是一個雙向通信過程,server和client都需要向對方發送close_notify來通知對方自己已經關閉,具體的關閉分為主動關閉、被動關閉和peer意外終止三種,具體的內容我貼一下從oracle官方的原文

For an orderly shutdown of an SSL/TLS connection, the SSL/TLS protocols require transmission of close messages. Therefore, when an application is done with the SSL/TLS connection, it should first obtain the close messages from the SSLEngine, then transmit them to the peer using its transport mechanism, and finally shut down the transport mechanism

In addition to an application explicitly closing the SSLEngine, the SSLEngine might be closed by the peer (via receipt of a close message while it is processing handshake data), or by the SSLEngine encountering an error while processing application or handshake data, indicated by throwing an SSLException. In such cases, the application should invoke SSLEngine.wrap() to get the close message and send it to the peer until SSLEngine.isOutboundDone() returns true (as shown in Example 6), or until the SSLEngineResult.getStatus() returns CLOSED.

In addition to orderly shutdowns, there can also be unexpected shutdowns when the transport link is severed before close messages are exchanged. In the previous examples, the application might get -1 or IOException when trying to read from the nonblocking SocketChannel, or get IOException when trying to write to the non-blocking SocketChannel. When you get to the end of your input data, you should call engine.closeInbound(), which will verify with the SSLEngine that the remote peer has closed cleanly from the SSL/TLS perspective. Then the application should still try to shut down cleanly by using the procedure in Example 6. Obviously, unlike SSLSocket, the application using SSLEngine must deal with more state transitions, statuses, and programming.

鏈接:https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLEngine

3、SSLEgnine工作原理

The following text describes this figure.

可以這樣理解,SSLEngine只是一個TLS協議數據的解析器和加密算法處理器,可以獨立於Socket單獨使用;

Application將plain text的數據通過SSLEngine.wrap(myAppBuf,myNetBuf)方法進行加密,MyNetBuf就是對app數據加密后的ByteBuffer對象;之后Socket調用write方法將MyNetBuf發送給peer。

Socket read獲取到的加密數據,通過SSLEngine.unwrap(peerNetBuf,peerAppBuf)進行解密,peerAppBuf就是解密后的數據,可以隨意解析。(NOTE:這里依然存在半包、粘包的問題)

4、SSLEngine Handshake階段的兩個重要的狀態標識

NOTE:建議看官方原文,這里表述的是我的理解。

SSLEngineResult.Status

OK:wrap(發送數據)或者unwrap(接受數據)成功,沒有錯誤。
CLOSED:對於handshake NEED_WRAP操作來說,就是當前端主動關閉了TLS通信;對於NEED_UNWRAP來說,就是peer主動調用了TLS通信,當前端獲取到了peer發送過來的close_notify message。
BUFFER_UNDERFLOW(buffer空閑):理論上來說,這個情況不會出現在handshake NEED_WRAP階段;對於NEED_UNWRAP階段來說,(1)peerNetBuf空間不足,需要擴容;(2)peerNetBuf讀取的數據出現了半包問題,需要繼續從socket中read。
BUFFER_OVERFLOW(buffer溢出):對於NEED_WRAP來說,myNetBuf空間不足,需要擴充或者清空;對於NEED_UNWRAP,peerAppBuf不足,需要擴容或者清空。

SSLEngineResult.handshakeStatus

FINISHED:握手完成。
NEED_TASK:需要等待一些task的完成,否則handshake無法繼續,出現這個情況時,后續engine的wrap和unwrap方法都會阻塞直到task完成。
NEED_UNWRAP:需要從peer端讀取新的數據,否則handshake無法繼續。
NEED_UNWRAP_AGAIN:與NEED_UNWRAP類似,但表示從peer讀取的數據已經存在於本地了,這個狀態下,不需要再重新走一遍網絡,只要解析已經接收到的數據就可以了。NOTE:在java8_u151中,並沒有這個枚舉類型。
NEED_WRAP:需要向peer端發送數據,否則handshake無法繼續。
NOT_HANDSHAKING:當前沒有處於handshake階段。

 

5、上代碼

NOTE:代碼使用無證書模式。

server端

  1.  
    package com.jsse.sslengine.newio.bio;
  2.  
     
  3.  
    import com.jsse.sslengine.HandshakeUtils;
  4.  
     
  5.  
    import javax.net.ssl.SSLContext;
  6.  
    import javax.net.ssl.SSLEngine;
  7.  
    import javax.net.ssl.SSLEngineResult;
  8.  
    import javax.net.ssl.SSLException;
  9.  
    import java.io.IOException;
  10.  
    import java.net.InetSocketAddress;
  11.  
    import java.nio.ByteBuffer;
  12.  
    import java.nio.channels.ServerSocketChannel;
  13.  
    import java.nio.channels.SocketChannel;
  14.  
    import java.nio.charset.StandardCharsets;
  15.  
    import java.security.KeyManagementException;
  16.  
    import java.security.NoSuchAlgorithmException;
  17.  
    import java.security.Security;
  18.  
     
  19.  
    /**
  20.  
    * 使用java7+的New IO API實現SSLEngine
  21.  
    * server端
  22.  
    */
  23.  
    public class BioServerEngine {
  24.  
    public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException {
  25.  
    Security.setProperty( "jdk.tls.disabledAlgorithms", "SSLv3, RC4, MD5withRSA, EC keySize < 224");
  26.  
    SSLContext sslContext = SSLContext.getInstance( "TLSv1.2");
  27.  
    sslContext.init( null,null,null);
  28.  
    try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
  29.  
    serverSocketChannel.configureBlocking( true);
  30.  
    serverSocketChannel.bind( new InetSocketAddress("localhost", 8080));
  31.  
    while (true) {
  32.  
    SSLEngine sslEngine = null;
  33.  
    try (SocketChannel socketChannel = serverSocketChannel.accept()) {
  34.  
    //每建立一個連接,就使用一個新的sslengine
  35.  
    sslEngine = sslContext.createSSLEngine();
  36.  
    sslEngine.setUseClientMode( false);
  37.  
    //使用匿名DH算法,通信雙方不再需要keystore
  38.  
    sslEngine.setEnabledCipherSuites( new String[]{"SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"});
  39.  
    //開始TLS協議的handshake階段
  40.  
    sslEngine.beginHandshake();
  41.  
    HandshakeUtils.SSLEngineResultDTO sslEngineResultDTO = new HandshakeUtils.SSLEngineResultDTO(sslEngine, socketChannel,
  42.  
    sslEngine.getSession().getApplicationBufferSize(), sslEngine.getSession().getPacketBufferSize());
  43.  
    if (!HandshakeUtils.handshakeBio(sslEngineResultDTO)) {
  44.  
    System.out.println( "握手失敗!!!!!!!");
  45.  
    } else {
  46.  
    System.out.println( "握手成功!!!!!!!!!!!!!!!!!!!!!!!!!");
  47.  
    sslEngineResultDTO.clearAllBuffer();
  48.  
    }
  49.  
    }
  50.  
    }
  51.  
    } catch (IOException e) {
  52.  
    e.printStackTrace();
  53.  
    }
  54.  
     
  55.  
    }
  56.  
    }

client端

  1.  
    package com.jsse.sslengine.newio.bio;
  2.  
     
  3.  
    import com.jsse.sslengine.HandshakeUtils;
  4.  
     
  5.  
    import javax.net.ssl.SSLContext;
  6.  
    import javax.net.ssl.SSLEngine;
  7.  
    import javax.net.ssl.SSLEngineResult;
  8.  
    import javax.net.ssl.SSLException;
  9.  
    import java.io.IOException;
  10.  
    import java.net.InetSocketAddress;
  11.  
    import java.nio.channels.SocketChannel;
  12.  
    import java.nio.charset.StandardCharsets;
  13.  
    import java.security.KeyManagementException;
  14.  
    import java.security.NoSuchAlgorithmException;
  15.  
    import java.security.Security;
  16.  
     
  17.  
    /**
  18.  
    * 使用java7+的New IO API實現SSLEngine
  19.  
    * client端
  20.  
    */
  21.  
    public class BioClientEngine {
  22.  
    public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException {
  23.  
    Security.setProperty( "jdk.tls.disabledAlgorithms", "SSLv3, RC4, MD5withRSA, EC keySize < 224");
  24.  
    SSLContext sslContext = SSLContext.getInstance( "TLSv1.2");
  25.  
    sslContext.init( null,null,null);
  26.  
    SSLEngine sslEngine = sslContext.createSSLEngine();
  27.  
    sslEngine.setUseClientMode( true);
  28.  
    sslEngine.setEnabledCipherSuites( new String[]{"SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"});
  29.  
    try (SocketChannel socketChannel = SocketChannel.open()) {
  30.  
    socketChannel.connect( new InetSocketAddress("localhost", 8080));
  31.  
    HandshakeUtils.SSLEngineResultDTO sslEngineResultDTO = new HandshakeUtils.SSLEngineResultDTO(sslEngine, socketChannel,
  32.  
    sslEngine.getSession().getApplicationBufferSize(), sslEngine.getSession().getPacketBufferSize());
  33.  
    sslEngine.beginHandshake();
  34.  
    if (HandshakeUtils.handshakeBio(sslEngineResultDTO)) {
  35.  
    System.out.println( "握手成功!!!!");
  36.  
    sslEngineResultDTO.clearAllBuffer();
  37.  
    byte[] bytes = "hello,i am client!".getBytes(StandardCharsets.UTF_8);
  38.  
    byte[] quitBytes = "quit".getBytes(StandardCharsets.UTF_8);
  39.  
    } else {
  40.  
    System.out.println( "握手失敗!!!!");
  41.  
    }
  42.  
     
  43.  
    } catch (IOException e) {
  44.  
    e.printStackTrace();
  45.  
    }
  46.  
    }
  47.  
    }

Handshake核心處理邏輯

  1.  
    package com.jsse.sslengine;
  2.  
     
  3.  
    import javax.net.ssl.SSLEngine;
  4.  
    import javax.net.ssl.SSLEngineResult;
  5.  
    import javax.net.ssl.SSLException;
  6.  
    import java.io.IOException;
  7.  
    import java.nio.ByteBuffer;
  8.  
    import java.nio.channels.SocketChannel;
  9.  
     
  10.  
    public class HandshakeUtils {
  11.  
    /**
  12.  
    * BIO的TLS Handshake協議處理流程
  13.  
    *
  14.  
    * @param sslEngineResultDTO
  15.  
    * @return true:握手成功;false:握手失敗
  16.  
    * @throws IllegalStateException 非法的狀態
  17.  
    * @throws IOException peer異常關閉
  18.  
    */
  19.  
    public static boolean handshakeBio(SSLEngineResultDTO sslEngineResultDTO) throws IOException {
  20.  
    SSLEngine sslEngine = sslEngineResultDTO.sslEngine;
  21.  
    boolean result = true;
  22.  
    SocketChannel socketChannel = sslEngineResultDTO.socketChannel;
  23.  
    while (sslEngine.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.FINISHED
  24.  
    && sslEngine.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
  25.  
    switch (sslEngine.getHandshakeStatus()) {
  26.  
    case NEED_WRAP:
  27.  
    /**
  28.  
    * NEED_WRAP階段需要注意:不能在這個階段返回false,但需要設置result=false
  29.  
    * 能夠直接返回false的只有UNWRAP階段
  30.  
    */
  31.  
    System.out.println( "NEED_WRAP");
  32.  
    int n = 0; //用於記錄發送的字節數
  33.  
    //1、判斷isOutboundDone,當true時,說明已經不需要再處理任何的NEED_WRAP操作了,因為已經顯示調用過closeOutbound,且就算執行wrap,
  34.  
    // SSLEngineReulst.STATUS也一定是CLOSED,沒有任何意義
  35.  
    if(sslEngine.isOutboundDone()) {
  36.  
    //判斷myNetBuf是否存在還沒有發送的數據,存在要發送出去
  37.  
    if(sslEngineResultDTO.myNetBuf.position()>0) {
  38.  
    sslEngineResultDTO.myNetBuf.flip();
  39.  
    while(sslEngineResultDTO.myNetBuf.hasRemaining()) {
  40.  
    n += sslEngineResultDTO.socketChannel.write(sslEngineResultDTO.myNetBuf);
  41.  
    }
  42.  
    }
  43.  
    System.out.println( "NEED_WRAP[CLOSED1]發送了["+n+"]個字節");
  44.  
    break;
  45.  
    }
  46.  
    //2、執行wrap操作
  47.  
    try {
  48.  
    SSLEngineResult sslEngineResult = sslEngine.wrap(sslEngineResultDTO.myAppBuf,sslEngineResultDTO.myNetBuf);
  49.  
    //3、處理sslEngineResult
  50.  
    if(handSSLStatus(sslEngineResult,1,sslEngineResultDTO)) {
  51.  
    //wrap處理成功
  52.  
    //將myNetBuf的數據寫入到socketChannel中
  53.  
    sslEngineResultDTO.myNetBuf.flip();
  54.  
    while(sslEngineResultDTO.myNetBuf.hasRemaining()) {
  55.  
    n += sslEngineResultDTO.socketChannel.write(sslEngineResultDTO.myNetBuf);
  56.  
    }
  57.  
    System.out.println( "NEED_WRAP發送了["+n+"]個字節");
  58.  
    //發送結束后,清空buffer
  59.  
    sslEngineResultDTO.myNetBuf.clear();
  60.  
    } else {
  61.  
    //wrap出現BUFFER_OVERFLOW
  62.  
    //此時的myNetBuf已經是擴容后的結果,需要重新執行NEED_WRAP,因此不需要任何操作
  63.  
    break;
  64.  
    }
  65.  
    } catch (SSLException e) {
  66.  
    //出現SSLException異常,按照官方SSLEngine API描述,此時會自動生成close_notify,我們只需要顯示調用closeoutbone保證wrap不會執行就可以了
  67.  
    e.printStackTrace();
  68.  
    sslEngine.closeOutbound();
  69.  
    result= false;
  70.  
    } catch (ClosedException e2) {
  71.  
    //出現這個異常,說明wrap操作已經完整結束,需要發送myNetBuf中所有未發送的數據
  72.  
    sslEngine.closeOutbound();
  73.  
    sslEngineResultDTO.myNetBuf.flip();
  74.  
    while(sslEngineResultDTO.myNetBuf.hasRemaining()) {
  75.  
    n += sslEngineResultDTO.socketChannel.write(sslEngineResultDTO.myNetBuf);
  76.  
    }
  77.  
    System.out.println( "NEED_WRAP[CLOSED]發送了["+n+"]個字節");
  78.  
    result= false;
  79.  
    }
  80.  
    break;
  81.  
    case NEED_UNWRAP:
  82.  
    System.out.println( "NEED_UNWRAP");
  83.  
    int rn = 0;//記錄socket read的字節數
  84.  
    boolean underflow = false;
  85.  
    //判斷inboundDone是否為true,true說明peer端發送了close_notify,當然,peer發送了close_notify也可能被unwrap捕獲,結果就是返回的CLOSED
  86.  
    if(sslEngine.isInboundDone()) {
  87.  
    //peer端發送關閉,此時需要判斷是否調用closeOutbound
  88.  
    if(sslEngine.isOutboundDone()) {
  89.  
    return false;
  90.  
    } else {
  91.  
    sslEngine.closeOutbound();
  92.  
    break;
  93.  
    }
  94.  
    }
  95.  
    //只要sslEngine.isInboundDone=false,就是可以執行unwrap操作的
  96.  
    //1、從socketChannel中讀取字節
  97.  
    rn = socketChannel.read(sslEngineResultDTO.peerNetBuf);
  98.  
    System.out.println( "NEED_UNWRAP READ["+rn+"]個字節");
  99.  
    if(rn<0) {
  100.  
    //這個判斷是針對non-bloking-io來說的,對於BIO,不會出現<0的情況,當時0時就會無限阻塞
  101.  
    if(!sslEngine.isInboundDone()) {
  102.  
    try {
  103.  
    //SSLEngine官方API要求,peer端非法關閉時(沒有發送close_notify),
  104.  
    // 需要調用closeInbound來標識sslengine后續不會再讀取任何peer data
  105.  
    sslEngine.closeInbound();
  106.  
    } catch (SSLException e) {
  107.  
    e.printStackTrace();
  108.  
    }
  109.  
    }
  110.  
    //當前端顯示發送close_notify
  111.  
    sslEngine.closeOutbound();
  112.  
    break;
  113.  
    }
  114.  
     
  115.  
    //2、執行unwrap操作
  116.  
    try {
  117.  
    SSLEngineResult sslEngineResult = null;
  118.  
    sslEngineResultDTO.peerNetBuf.flip();
  119.  
    do {
  120.  
    int unwrapSize = sslEngineResultDTO.peerNetBuf.position();
  121.  
    sslEngineResult = sslEngine.unwrap(sslEngineResultDTO.peerNetBuf,sslEngineResultDTO.peerAppBuf);
  122.  
    if(handSSLStatus(sslEngineResult,2,sslEngineResultDTO)) {
  123.  
    //解析成功,在do while循環中繼續運行,不對peerNetBuf做任何操作
  124.  
    unwrapSize = sslEngineResultDTO.peerNetBuf.position()-unwrapSize;
  125.  
    System.out.println( "unwrap了["+unwrapSize+"]個字節");
  126.  
    } else {
  127.  
    //發送了BUFFER_OVERFLOW或者BUFFER_UNDERFLOW
  128.  
    //對於UNWRAP,BUFFER_OVERFLOW應該不會出現
  129.  
    //對於BUFFER_UNDERFLOW,出現的原因可能是半包或者peerNetBuf空間不足,半包很簡單,不需要任何處理,再從socketChannel繼續讀取
  130.  
    //如果是peerNetBuf空間不足,那么此時的peerNetBuf已經是擴容后的,且position=N(N時擴容前的position位置),limit=capacity
  131.  
    //因此這里只需要重新執行NEED_UNWRAP就可以了
  132.  
    underflow = true;
  133.  
    sslEngineResultDTO.peerNetBuf.compact();
  134.  
    break;
  135.  
    }
  136.  
    } while(sslEngineResultDTO.peerNetBuf.hasRemaining()||sslEngineResult.bytesProduced()>0);
  137.  
    //當跳出循環時,需要判斷是正常跳出還是因為BUFFER_UNDERFLOWt跳出的
  138.  
    if(!underflow) {
  139.  
    //半包情況下,一定會出現buffer_underflow,因此不會進入這里
  140.  
    //粘包情況下,如果恰好是N個完整的包,那么peerNetBuf剛好消費完,只需要clear就可以了;
  141.  
    //如果粘包中包含半包,則與半包情況相同,不會進入這里
  142.  
    //理想情況下,只需要clear就可以了
  143.  
    sslEngineResultDTO.peerNetBuf.clear();
  144.  
    }
  145.  
    } catch (SSLException e) {
  146.  
    e.printStackTrace();
  147.  
    //SSLEngine自動生成close_notify,我們只需要顯示調用closeOutbound即可
  148.  
    sslEngine.closeOutbound();
  149.  
    } catch (ClosedException e2) {
  150.  
    //說明peer端發送了close_notify,此時發送closeOutbound
  151.  
    if(sslEngine.isOutboundDone()) {
  152.  
    //這個判斷說明,當前端是主動關閉,peer端響應了close_notify
  153.  
    return false;
  154.  
    }
  155.  
    sslEngine.closeOutbound();
  156.  
    }
  157.  
    break;
  158.  
    case NEED_TASK:
  159.  
    System.out.println( "NEED_TASK");
  160.  
    //使用當前線程處理handshake中的子任務,一般就是加密解密和計算public key的過程
  161.  
    sslEngine.getDelegatedTask().run();
  162.  
    break;
  163.  
    case FINISHED:
  164.  
    case NOT_HANDSHAKING:
  165.  
    //這兩個狀態是不會出現的
  166.  
    break;
  167.  
    default:
  168.  
    throw new IllegalStateException("invalid HandshakeStatus:" + sslEngine.getHandshakeStatus());
  169.  
    }
  170.  
    }
  171.  
     
  172.  
    return result;
  173.  
    }
  174.  
     
  175.  
    /**
  176.  
    * wrap和unwrap處理
  177.  
    *
  178.  
    * @param sslEngineResult
  179.  
    * @param type 1:wrap,2:unwrap
  180.  
    * @param sslEngineResultDTO
  181.  
    * @return true:操作成功;false:需要繼續執行
  182.  
    * @throws IllegalStateException 狀態編碼不合法,理論上不會出現
  183.  
    * @throws ClosedException sslEngineResult返回了CLOSED,需要單獨處理
  184.  
    */
  185.  
    public static boolean handSSLStatus(SSLEngineResult sslEngineResult, int type, SSLEngineResultDTO sslEngineResultDTO) {
  186.  
    String typeStr = type == 1 ? "WRAP" : "UNWRAP";
  187.  
    switch (sslEngineResult.getStatus()) {
  188.  
    case OK:
  189.  
    System.out.format( "%s-OK%n", typeStr);
  190.  
    return true;
  191.  
    case CLOSED:
  192.  
    System.out.format( "%s-CLOSED%n", typeStr);
  193.  
    throw new ClosedException();
  194.  
    case BUFFER_OVERFLOW:
  195.  
    System.out.format( "%s-BUFFER_OVERFLOW%n", typeStr);
  196.  
    //sink buffer空間不足
  197.  
    if (type == 1) { //wrap階段,說明是mynetbuf空間不足
  198.  
    //進行擴容
  199.  
    int netBufSize = sslEngineResultDTO.sslEngine.getSession().getPacketBufferSize();
  200.  
    ByteBuffer newBuffer = null;
  201.  
    if (netBufSize > sslEngineResultDTO.myNetBuf.capacity()) {
  202.  
    newBuffer = ByteBuffer.allocate(netBufSize);
  203.  
    } else {
  204.  
    newBuffer = ByteBuffer.allocate(sslEngineResultDTO.myNetBuf.capacity() * 2);
  205.  
    }
  206.  
    if (sslEngineResultDTO.myNetBuf.position() != 0) {
  207.  
    sslEngineResultDTO.myNetBuf.flip();
  208.  
    }
  209.  
    newBuffer.put(sslEngineResultDTO.myNetBuf);
  210.  
    sslEngineResultDTO.myNetBuf = newBuffer;
  211.  
    //注意!!!這里不要對newBuffer進行flip,要保證原來已經wrap進去的數據不丟失
  212.  
    } else { //unwrap階段,說明是peerappBuf空間不足
  213.  
    int appBufSize = sslEngineResultDTO.sslEngine.getSession().getApplicationBufferSize();
  214.  
    ByteBuffer newBuffer = null;
  215.  
    if (appBufSize > sslEngineResultDTO.peerAppBuf.capacity()) {
  216.  
    newBuffer = ByteBuffer.allocate(appBufSize);
  217.  
    } else {
  218.  
    newBuffer = ByteBuffer.allocate(sslEngineResultDTO.peerAppBuf.capacity() * 2);
  219.  
    }
  220.  
    if (sslEngineResultDTO.peerAppBuf.position() != 0) {
  221.  
    sslEngineResultDTO.peerAppBuf.flip();
  222.  
    }
  223.  
    newBuffer.put(sslEngineResultDTO.peerAppBuf);
  224.  
    sslEngineResultDTO.peerAppBuf = newBuffer;
  225.  
    //注意:!!!同樣不能對newbuffer進行flip
  226.  
    }
  227.  
    return false;
  228.  
    case BUFFER_UNDERFLOW:
  229.  
    System.out.format( "%s-BUFFER_UNDERFLOW%n", typeStr);
  230.  
    //數據讀取不完整,可能是半包問題,也可能是source buffer空間不足造成無法從socket buffer中完整的加載數據
  231.  
    //理論上,這個狀態只會出現在unwrap階段
  232.  
    if (type == 2) {
  233.  
    //判斷出到底是peerNetBuf的空間不足還是讀取的數據不完整
  234.  
    if (sslEngineResultDTO.peerNetBuf.capacity() < sslEngineResultDTO.sslEngine.getSession().getPacketBufferSize()) {
  235.  
    //這種情況下,一定是需要擴容的
  236.  
    ByteBuffer newBuffer = ByteBuffer.allocate(sslEngineResultDTO.sslEngine.getSession().getPacketBufferSize());
  237.  
    sslEngineResultDTO.peerNetBuf.flip();
  238.  
    newBuffer.put(sslEngineResultDTO.peerNetBuf);
  239.  
    sslEngineResultDTO.peerNetBuf = newBuffer;
  240.  
    } else {
  241.  
    //這種情況下,可能需要擴容
  242.  
    //如果position>limit(capacity)*0.75,則擴容到2倍,否則認為是數據讀取不完整,需要再次讀取
  243.  
    //數據不完整,不對peernetbuffer做任何操作,這種就是半包問題
  244.  
    if (sslEngineResultDTO.peerNetBuf.position() >= sslEngineResultDTO.peerNetBuf.capacity() * 0.75) {
  245.  
    ByteBuffer newBuffer = ByteBuffer.allocate(sslEngineResultDTO.peerNetBuf.capacity() * 2);
  246.  
    sslEngineResultDTO.peerNetBuf.flip();
  247.  
    newBuffer.put(sslEngineResultDTO.peerNetBuf);
  248.  
    sslEngineResultDTO.peerNetBuf = newBuffer;
  249.  
    }
  250.  
    }
  251.  
    } else {
  252.  
    //這種情況是不合理的,打印,不做任何處理
  253.  
    System.out.println( "WARN!!!!,在WRAP階段出現了BUFFER_UNDERFLOW");
  254.  
    }
  255.  
    return false;
  256.  
    default:
  257.  
    throw new IllegalStateException("invalid sslEngineResult:" + sslEngineResult.getStatus());
  258.  
    }
  259.  
    }
  260.  
     
  261.  
    public static class ClosedException extends RuntimeException {
  262.  
     
  263.  
    }
  264.  
     
  265.  
    public static class SSLEngineResultDTO {
  266.  
    public SSLEngine sslEngine;
  267.  
    public SocketChannel socketChannel;
  268.  
    public ByteBuffer myAppBuf;
  269.  
    public ByteBuffer myNetBuf;
  270.  
    public ByteBuffer peerAppBuf;
  271.  
    public ByteBuffer peerNetBuf;
  272.  
     
  273.  
    public SSLEngineResultDTO(SSLEngine sslEngine, SocketChannel socketChannel, int appBufSize, int netBufSize) {
  274.  
    this.sslEngine = sslEngine;
  275.  
    this.socketChannel = socketChannel;
  276.  
    this.myAppBuf = this.peerAppBuf = ByteBuffer.allocate(appBufSize);
  277.  
    this.myNetBuf = this.peerNetBuf = ByteBuffer.allocate(netBufSize);
  278.  
    }
  279.  
     
  280.  
    public void clearAllBuffer() {
  281.  
    this.myAppBuf.clear();
  282.  
    this.myNetBuf.clear();
  283.  
    this.peerAppBuf.clear();
  284.  
    this.peerNetBuf.clear();
  285.  
    }
  286.  
    }
  287.  
    }

6、遇到的問題

問題1:使用Socket API時,出現如下錯誤

產生原因:

Socket API通信雙方在read和write時,需要使用message split character來解決粘包問題(其實最主要的還是解決read方法什么時候結束的問題),在前期開發中,使用過\n和\0作為分隔符,然而都曾出現過TSL協議無法解析的問題,下面是我截取的client端和server端的一部分handshake階段內容

client:

75字節是client keyExchange階段發送的報文內容,需要再額外增加1字節的\0,總共76字節;

在wireshark中,可以明顯看到有兩個PSH+ACK報文,分別是75字節和1字節;

server:

server端按照\0進行read,當遇到\0時,就認為是一個完整的tls報文(其中\0不會作為報文內容),此時發現,76個字節只接收了74個,理論上應該接收75個字節,也就是說,TSL的報文中包含\0這個字符,通過wireshark驗證,也確實如此。

結論:

在使用Socket+SSLengine時,報文切割字符的方式比較危險,建議通過設置SO_TIMEOUT來實現。

 

問題2:使用SocketChannel BIO模型時,server端在unwrap階段阻塞

產生的原因:

SocketChannel BIO模型下,read方法是無限阻塞模式的,SO_TIMEOUT並沒有作用;

client端的changeCipherSpec和Finished的報文內容是分兩次進行的NEED_WRAP,但是server端在NEED_UNWRAP時卻是一次性從socketChannel中read出來的(也不清楚是因為粘包問題還是SSLEngine底層自動組合封裝后一次性發送的,如果有知道的大佬,請指點我一下,謝謝。);

server端在unwrap client端的changeCipherSpec時,從socketChannel read出來的是changeCipherSpec和Finished兩個報文內容,但是在執行sslengine.unwrap(peerNetBuf,peerAppBuf)時,只處理了changeCipherSpec,然后修改handshakestatus=NEED_UNWRAP,進入第二次循環的NEED_UNWRAP處理邏輯中,也就造成了第二次的socketChannel.read方法的執行,因為此時Client端已經發送完Finished,進入了NEED_UNWRAP階段,也就不會再發送任何數據給server,因此server就會阻塞在SocketChannel.read方法上。

具體的分析截圖:

client端handshake

server端的handshake:

上圖中可以很明顯的發現,client端分兩次發送了6個字節和45個字節,但server端在一次unwrap中就讀取了51個字節

結論:

在demo的handshake核心處理邏輯代碼中,已經做了處理,具體看代碼。

 

問題3:handshake階段,如何處理shutdown

這個問題需要參考SSLEngine的API文檔,里面的描述很清楚。

鏈接:https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SSLEngine.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM