/* Copyright (C) 2012,2014 Brian P. Hinz
* Copyright (C) 2012 D. R. Commander. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA
*/
package com.tigervnc.network;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import javax.net.ssl.*;
import javax.net.ssl.SSLEngineResult.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import com.tigervnc.rdr.FdInStream;
import com.tigervnc.rdr.FdOutStream;
public class SSLEngineManager {
private SSLEngine engine = null;
private int appBufSize;
private int pktBufSize;
private ByteBuffer myAppData;
private ByteBuffer myNetData;
private ByteBuffer peerAppData;
private ByteBuffer peerNetData;
private Executor executor;
private FdInStream in;
private FdOutStream os;
public SSLEngineManager(SSLEngine sslEngine, FdInStream is_,
FdOutStream os_) throws IOException {
in = is_;
os = os_;
engine = sslEngine;
executor = Executors.newSingleThreadExecutor();
pktBufSize = engine.getSession().getPacketBufferSize();
appBufSize = engine.getSession().getApplicationBufferSize();
myAppData =
ByteBuffer.allocate(Math.max(appBufSize, os.getBufSize()));
myNetData = ByteBuffer.allocate(pktBufSize);
peerAppData = ByteBuffer.allocate(appBufSize);
peerNetData = ByteBuffer.allocate(pktBufSize);
}
public void doHandshake() throws Exception {
// Begin handshake
engine.beginHandshake();
SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
// Process handshaking message
while (hs != SSLEngineResult.HandshakeStatus.FINISHED &&
hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
switch (hs) {
case NEED_UNWRAP:
// Receive handshaking data from peer
peerNetData.flip();
SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
peerNetData.compact();
hs = res.getHandshakeStatus();
// Check status
switch (res.getStatus()) {
case BUFFER_UNDERFLOW:
int max = Math.min(peerNetData.remaining(), in.getBufSize());
int m = in.check(1, max, true);
int pos = peerNetData.position();
in.readBytes(peerNetData.array(), pos, m);
peerNetData.position(pos+m);
peerNetData.flip();
peerNetData.compact();
break;
case OK:
// Process incoming handshaking data
break;
case CLOSED:
engine.closeInbound();
break;
}
break;
case NEED_WRAP:
// Empty the local network packet buffer.
myNetData.clear();
// Generate handshaking data
res = engine.wrap(myAppData, myNetData);
hs = res.getHandshakeStatus();
// Check status
switch (res.getStatus()) {
case OK:
myAppData.compact();
myNetData.flip();
os.writeBytes(myNetData.array(), 0, myNetData.remaining());
os.flush();
myNetData.clear();
break;
case BUFFER_OVERFLOW:
// FIXME: How much larger should the buffer be?
break;
case CLOSED:
engine.closeOutbound();
break;
}
break;
case NEED_TASK:
// Handle blocking tasks
executeTasks();
break;
}
hs = engine.getHandshakeStatus();
}
}
private void executeTasks() {
Runnable task;
while ((task = engine.getDelegatedTask()) != null) {
executor.execute(task);
}
}
public int read(byte[] data, int dataPtr, int length) throws IOException {
// Read SSL/TLS encoded data from peer
int bytesRead = 0;
peerNetData.flip();
SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
peerNetData.compact();
switch (res.getStatus()) {
case OK :
bytesRead = Math.min(length, res.bytesProduced());
peerAppData.flip();
peerAppData.get(data, dataPtr, bytesRead);
peerAppData.compact();
break;
case BUFFER_UNDERFLOW:
// need more net data
int pos = peerNetData.position();
// attempt to drain the underlying buffer first
int need = peerNetData.remaining();
int avail = in.check(1, in.getBufSize(), false);
if (avail < need)
avail = in.check(1, Math.min(need, in.getBufSize()), true);
in.readBytes(peerNetData.array(), pos, Math.min(need, avail));
peerNetData.position(pos+Math.min(need, avail));
break;
case CLOSED:
engine.closeInbound();
break;
}
return bytesRead;
}
public int write(byte[] data, int dataPtr, int length) throws IOException {
int n = 0;
myAppData.put(data, dataPtr, length);
myAppData.flip();
while (myAppData.hasRemaining()) {
SSLEngineResult res = engine.wrap(myAppData, myNetData);
n += res.bytesConsumed();
switch (res.getStatus()) {
case OK:
break;
case BUFFER_OVERFLOW:
// Make room in the buffer by flushing the outstream
myNetData.flip();
os.writeBytes(myNetData.array(), 0, myNetData.remaining());
os.flush();
myNetData.clear();
break;
case CLOSED:
engine.closeOutbound();
break;
}
}
myAppData.clear();
myNetData.flip();
os.writeBytes(myNetData.array(), 0, myNetData.remaining());
os.flush();
myNetData.clear();
return n;
}
public SSLSession getSession() {
return engine.getSession();
}
}