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

posted @ 2021-01-14 20:02  zJanly  阅读(1292)  评论(0编辑  收藏  举报