0

几个月前,我使用 NIO 和 SSLEngine 开发了一个 SSL Web 服务器。GET 请求和小型 POST 请求(大约 10KB 以下)一样有效。但是,当我发布比这更大的东西时,我会收到零星的 SSLException 异常。

例如,如果我发布一个小图像 (~16KB),我可以看到 3 条 TLS 记录代表应用程序数据。第一个是 HTTP 标头,另外两个包含有效负载。这是数据包有多小的感觉:

1: 613 bytes
2: 16341 bytes
3: 549 bytes

在我的代码中,我调用 SSLEngine 来解包/解密 TLS 记录(应用程序数据)。SSLEngine 在尝试解开第二条记录时经常会阻塞。这是错误:

javax.net.ssl.SSLException: Invalid padding
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readRecord(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(Unknown Source)
    at javax.net.ssl.SSLEngine.unwrap(Unknown Source)
    at javaxt.http.servlet.HttpServletRequest.getApplicationData(HttpServletRequest.java:1793)
    ...

    Caused by: javax.crypto.BadPaddingException: Invalid TLS padding: 199
    ...

这是引发错误的代码。

private byte[] getApplicationData() throws IOException {

  //Read the next 5 bytes from the socket channel. This should contain TLS
  //record information.
    if (recordHeader==null){
        recordHeader = ByteBuffer.allocateDirect(5);
        read(recordHeader);
    }

  //Verify that the message contains application data
    recordHeader.rewind();
    if(recordHeader.get()!=23) throw new IOException();

  //Get the length of the TLS record
    recordHeader.position(3);
    int recordLength = Integer.parseInt(getHex(recordHeader) + getHex(recordHeader), 16);
    recordHeader.rewind();

  //Read the TLS record
    ByteBuffer recordData = ByteBuffer.allocateDirect(recordLength);
    read(recordData);
    recordData.rewind();

  //Merge the TLS header and record data into a single buffer
    ByteBuffer tlsRecord = ByteBuffer.allocateDirect(recordLength+recordHeader.capacity());
    tlsRecord.put(recordHeader);
    tlsRecord.put(recordData);
    tlsRecord.rewind();

  //Decrypt the application data
    ByteBuffer output = ByteBuffer.allocateDirect(tlsRecord.capacity());
    SSLEngineResult serverResult = sslEngine.unwrap(tlsRecord, output);
    runDelegatedTasks(serverResult, sslEngine);


    byte[] arr = new byte[output.position()];
    output.rewind();
    output.get(arr);


  //Clean up
    recordHeader.clear();
    recordData.clear();
    tlsRecord.clear();
    output.clear();
    recordHeader = recordData = tlsRecord = output = null;


  //Return array
    return arr;
}

SSLException 在这一行被抛出:

SSLEngineResult serverResult = sslEngine.unwrap(tlsRecord, output);

此错误偶尔发生。有时我看到它,有时我没有。通常,如果我确实看到错误并简单地重新发布数据(例如刷新浏览器),一切正常(没有错误)。

我无论如何都不是 SSL 专家,所以我不知道如何最好地调试这个问题。我很确定我正确地调用了 unwrap 方法。第二条记录是否以某种方式损坏?密码是否已更改,我需要重新启动握手?遇到此错误时我该怎么办?

其他几点:

  • 我正在使用 Sun/Oracle JDK 6 和与之捆绑的 Java 安全套接字扩展 (JSSE)。
  • 在使用 Mobile Safari、iOS 6 时,我经常看到这个错误。我很少遇到 FireFox 的这个问题。

提前致谢!

4

2 回答 2

3

你做这一切都是错的。

当您需要传入数据时,您应该只调用 unwrap() 。unwrap() 将通过其返回状态告诉您在必要时(BUFFER_UNDERFLOW)从网络读取;它还可能会告诉您运行任务、执行包装等。您不应该自行读取网络、查看 TLS 记录、读取长度、读取数据、重新组装它们并将它们提供给 SSLEngine。它已经做到了这一切。你的代码是颠倒的。

同样在写的时候,调用wrap()即可。它会告诉您何时写入网络(BUFFER_OVERFLOW)、运行任务等。

于 2013-01-12T23:38:46.177 回答
-1

经过长时间的中断,我终于解决了这个问题。问题是当我读取 TLS 记录时,我假设我的 read() 方法正在返回所有请求的字节(即 recordData ByteBuffer 已满)。那不是真的。结果, sslEngine.unwrap() 调用被炸毁了。

为了解决这个问题,我简单地替换了这个:

  //Read the TLS record
    ByteBuffer recordData = ByteBuffer.allocateDirect(recordLength);
    read(recordData)

有了这个:

  //Read the TLS record
    ByteBuffer recordData = ByteBuffer.allocateDirect(recordLength);
    int ttl = read(recordData);
    if (ttl<recordLength){
        while (ttl<recordLength){
            recordData.position(ttl);
            ByteBuffer tmp = ByteBuffer.allocateDirect(recordLength-ttl);
            ttl += read(tmp);
            recordData.put(tmp);
        }
        recordData.rewind();
    }

一旦 recordData ByteBuffer 被填满,SSLEngine unwrap() 方法就可以完美运行。

于 2013-07-19T16:22:38.010 回答