package com.netifera.platform.net.sockets.internal; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.ClosedChannelException; import java.util.concurrent.ExecutionException; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import com.netifera.platform.net.sockets.AsynchronousChannel; import com.netifera.platform.net.sockets.TCPChannel; /** * Upgrade a ByteChannel for SSL. * * <p> * Change Log: * </p> * <ul> * <li>v1.0.0 - First public release.</li> * </ul> * * <p> * This source code is given to the Public Domain. Do what you want with it. * This software comes with no guarantees or warranties. Please visit <a * href="http://perso.wanadoo.fr/reuse/sslbytechannel/">http://perso.wanadoo.fr/reuse/sslbytechannel/</a> * periodically to check for updates or to contribute improvements. * </p> * * @author David Crosson * @author david.crosson@wanadoo.fr * @version 1.0.0 */ public class SSLChannel implements ByteChannel, AsynchronousChannel { private TCPChannel wrappedChannel; private boolean closed = false; private SSLEngine engine; private final ByteBuffer inAppData; private final ByteBuffer outAppData; private final ByteBuffer inNetData; private final ByteBuffer outNetData; /** * Creates a new instance of SSLChannel * * @param wrappedChannel * The byte channel on which this ssl channel is built. This * channel contains encrypted data. * @param engine * A SSLEngine instance that will remember SSL current context. * Warning, such an instance CAN NOT be shared between multiple * SSLChannel. */ public SSLChannel(TCPChannel wrappedChannel, SSLEngine engine) { this.wrappedChannel = wrappedChannel; this.engine = engine; SSLSession session = engine.getSession(); inAppData = ByteBuffer.allocate(session.getApplicationBufferSize()); outAppData = ByteBuffer.allocate(session.getApplicationBufferSize()); inNetData = ByteBuffer.allocate(session.getPacketBufferSize()); outNetData = ByteBuffer.allocate(session.getPacketBufferSize()); } /** * Ends SSL operation and close the wrapped byte channel * * @throws java.io.IOException * May be raised by close operation on wrapped byte channel */ public void close() throws java.io.IOException { if (!closed) { try { engine.closeOutbound(); sslLoop(wrap()); wrappedChannel.close(); } finally { closed = true; } } } /** * Is the channel open ? * * @return true if the channel is still open */ public boolean isOpen() { return !closed; } /** * Fill the given buffer with some bytes and return the number of bytes * added in the buffer.<br> * This method may return immediately with nothing added in the buffer. This * method must be use exactly in the same way of ByteChannel read operation, * so be careful with buffer position, limit, ... Check corresponding * javadoc. * * @param byteBuffer * The buffer that will received read bytes * @throws java.io.IOException * May be raised by ByteChannel read operation * @return The number of bytes read */ public int read(java.nio.ByteBuffer byteBuffer) throws java.io.IOException { if (isOpen()) { try { @SuppressWarnings("unused") SSLEngineResult r = sslLoop(unwrap()); } catch (SSLException e) { System.err.println("SSLException while reading " + e);// TODO // : // Better // SSL // Exception // management // must // be // done } catch (ClosedChannelException e) { close(); } } inAppData.flip(); int posBefore = inAppData.position(); byteBuffer.put(inAppData); int posAfter = inAppData.position(); inAppData.compact(); if (posAfter - posBefore > 0) return posAfter - posBefore; if (isOpen()) return 0; else return -1; } /** * Write remaining bytes of the given byte buffer. This method may return * immediately with nothing written. This method must be use exactly in the * same way of ByteChannel write operation, so be careful with buffer * position, limit, ... Check corresponding javadoc. * * @param byteBuffer * buffer with remaining bytes to write * @throws java.io.IOException * May be raised by ByteChannel write operation * @return The number of bytes written */ public int write(java.nio.ByteBuffer byteBuffer) throws java.io.IOException { if (!isOpen()) return 0; int posBefore, posAfter; posBefore = byteBuffer.position(); if (byteBuffer.remaining() < outAppData.remaining()) { outAppData.put(byteBuffer); // throw a BufferOverflowException if // byteBuffer.remaining() > // outAppData.remaining() } else { while (byteBuffer.hasRemaining() && outAppData.hasRemaining()) { outAppData.put(byteBuffer.get()); } } posAfter = byteBuffer.position(); if (isOpen()) { try { while (true) { SSLEngineResult r = sslLoop(wrap()); // System.out.println(r); if (r.bytesConsumed() == 0 && r.bytesProduced() == 0) break; } ; } catch (SSLException e) { e.printStackTrace(); System.err.println("SSLException while reading " + e); // TODO // : // Better // SSL // Exception // management // must // be // done } catch (ClosedChannelException e) { e.printStackTrace(); close(); } } return posAfter - posBefore; } private SSLEngineResult unwrap() throws IOException, SSLException { try { while (wrappedChannel.read(inNetData).get() > 0) ; } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } inNetData.flip(); SSLEngineResult ser = engine.unwrap(inNetData, inAppData); inNetData.compact(); // System.err.println(ser); return ser; } private SSLEngineResult wrap() throws IOException, SSLException { SSLEngineResult ser = null; outAppData.flip(); ser = engine.wrap(outAppData, outNetData); outAppData.compact(); outNetData.flip(); while (outNetData.hasRemaining()) try { wrappedChannel.write(outNetData).get(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } outNetData.compact(); // System.err.println(ser); return ser; } @SuppressWarnings({ "incomplete-switch", "fallthrough" }) private SSLEngineResult sslLoop(SSLEngineResult ser) throws SSLException, IOException { System.out.println("sslLoop " + ser); if (ser == null) return ser; System.err.println(String.format("%s - %s\n", ser.getStatus() .toString(), ser.getHandshakeStatus().toString())); while (ser.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.FINISHED && ser.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { switch (ser.getHandshakeStatus()) { case NEED_TASK: // Executor exec = Executors.newSingleThreadExecutor(); Runnable task; while ((task = engine.getDelegatedTask()) != null) { // exec.execute(task); task.run(); } // Must continue with wrap as data must be sent case NEED_WRAP: ser = wrap(); break; case NEED_UNWRAP: ser = unwrap(); break; } } switch (ser.getStatus()) { case CLOSED: System.err .println("SSLEngine operations finishes, closing the socket"); try { wrappedChannel.close(); } finally { closed = true; } break; } return ser; } }