Generating and Processing SSL/TLS data

The two main SSLEngine methods wrap() and unwrap() are responsible for generating and consuming network data respectively. Depending on the state of the SSLEngine, this data might be handshake or application data.

Each SSLEngine has several phases during its lifetime. Before application data can be sent/received, the SSL/TLS protocol requires a handshake to establish cryptographic parameters. This handshake requires a series of back-and-forth steps by the SSLEngine. The TLS 1.2 Handshake can provide more details about the handshake itself.

During the initial handshaking, wrap() and unwrap() generate and consume handshake data, and the application is responsible for transporting the data. The wrap()/unwrap() sequence is repeated until the handshake is finished. Each SSLEngine operation generates a SSLEngineResult, of which the SSLEngineResult.HandshakeStatus field is used to determine what operation needs to occur next to move the handshake along.

A typical handshake might look like this:
Table 1. A typical handshake sequence
client SSL/TLS message HSStatus
wrap() ClientHello NEED_UNWRAP
unwrap() ServerHello/Cert/ServerHelloDone NEED_WRAP
wrap() ClientKeyExchange NEED_WRAP
wrap() ChangeCipherSpec NEED_WRAP
wrap() Finished NEED_UNWRAP
unwrap() ChangeCipherSpec NEED_UNWRAP
unwrap() Finished FINISHED

Now that handshaking is complete, further calls to wrap() will attempt to consume application data and packages it for transport. unwrap() attempts the opposite.

To send data to the peer, the application first supplies the data that it wants to send to SSLEngine via SSLEngine.wrap() to obtain the corresponding SSL/TLS encoded data. The application then sends the encoded data to the peer using its chosen transport mechanism. When the application receives the SSL/TLS encoded data from the peer via the transport mechanism, it supplies this data to the SSLEngine via SSLEngine.unwrap() to obtain the plaintext data sent by the peer.

Here is an example of an SSL application that is using a non-blocking SocketChannel to communicate with its peer. (It can be made more robust and scalable by using a Selector with the non-blocking SocketChannel.) The following sample code sends the string "hello" to its peer, by encoding it using the SSLEngine created in the previous example. It uses information from the SSLSession to determine how large to make the byte buffers.
// Create a non-blocking socket channel
SocketChannel socketChannel = SocketChannel.open();

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(hostname, port));


// Complete connection
while (!socketChannel.finishedConnect()) {
 
// do something until connect completed
}


// Create byte buffers to use for holding application and encoded data

SSLSession session = engine.getSession();

ByteBuffer myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());

ByteBuffer myNetData = ByteBuffer.allocate(session.getPacketBufferSize());

ByteBuffer peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());

ByteBuffer peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());


// Do initial handshake
doHandshake(socketChannel, engine, myNetData, peerNetData);


myAppData.put("hello".getBytes());
myAppData.flip();


while (myAppData.hasRemaining()) {
 
 // Generate SSL/TLS encoded data (handshake or application data)
 
 SSLEngineResult res = engine.wrap(myAppData, myNetData);
 

 // Process status of call
 if (res.getStatus() == SSLEngineResult.Status.OK) {
  
  myAppData.compact();
  
  // Send SSL/TLS encoded data to peer
  
  while(myNetData.hasRemaining()) {
   
   int num = socketChannel.write(myNetData);
   
   if (num == -1) {
    // handle closed channel
   } else if (num == 0) {
    
    // no bytes written; try again later
   }
  }
 }
 

 // Handle other status: BUFFER_OVERFLOW, CLOSED
 ...
} 
The following code reads data from the same non-blocking SocketChannel and extracts the plaintext data from it by using the SSLEngine created previously. Each iteration of this code may or may not produce any plaintext data, depending on whether handshaking is in progress.
// Read SSL/TLS encoded data from peer
int num = socketChannel.read(peerNetData);

if (num == -1) {
 // Handle closed channel
} else if (num == 0) {
 
 // No bytes read; try again ...
} else {
 // Process incoming data
 
 peerNetData.flip();
 res = engine.unwrap(peerNetData, peerAppData);
 

 if (res.getStatus() == SSLEngineResult.Status.OK) {
  
  peerNetData.compact();
  
  if (peerAppData.hasRemaining()) {
   // Use peerAppData
  }
 }
 
 // Handle other status: BUFFER_OVERFLOW, BUFFER_UNDERFLOW, CLOSED
 ...
}