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.
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.
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
...
}
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
...
}