Several months ago I developed an SSL web server using NIO and the SSLEngine. GET requests work great as do small POST requests (under ~10KB). However, I'm getting sporadic SSLException exceptions, when I POST anything larger than that.
For example, if I POST a small image (~16KB), I can see 3 TLS records representing application data. The first is the HTTP header, and the other two contain the payload. Here's a sense of how small the packets are:
1: 613 bytes
2: 16341 bytes
3: 549 bytes
In my code, I call the SSLEngine to unwrap/decrypt the TLS records (application data). Frequently, the SSLEngine chokes when it tries to unwrap the second record. Here's the error:
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
...
Here's the code that's throwing the error.
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;
}
The SSLException is thrown at this line:
SSLEngineResult serverResult = sslEngine.unwrap(tlsRecord, output);
This error occurs sporadically. Sometimes I see it, sometimes I don't. Oftentimes, if I do see the error and simply re-POST the data (e.g. refresh the browser), everything works great (no errors).
I am not an SSL expert by any means so I don't know how best to debug the issue. I'm pretty sure I'm calling the unwrap method correctly. Is the second record somehow corrupt? Has the cypher changed and I need to re-initiate a handshake? What do I do when I encounter this error?
Couple other points:
Thanks in advance!
After a long hiatus, I finally got around to resolving this issue. The problem was that when I was reading the TLS record, I was assuming that my read() method was returning all the requested bytes (i.e. the recordData ByteBuffer was full). That was not true. As a result, the sslEngine.unwrap() call was bombing out.
To resolve the issue, I simply replaced this:
//Read the TLS record
ByteBuffer recordData = ByteBuffer.allocateDirect(recordLength);
read(recordData)
With this:
//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();
}
Once the recordData ByteBuffer was filled, the SSLEngine unwrap() method worked flawlessly.