All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xsocket.connection.spi.SSLProcessor Maven / Gradle / Ivy

/*
 *  Copyright (c) xsocket.org, 2006 - 2008. 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
 * The latest copy of this software may be found on http://www.xsocket.org/
 */
package org.xsocket.connection.spi;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;


import org.xsocket.DataConverter;



/**
 * ssl processor
 * 
 * @author [email protected]
 */
final class SSLProcessor {
	
	private static final Logger LOG = Logger.getLogger(SSLProcessor.class.getName());
	

	private SSLEngine sslEngine = null;
	
	private boolean isClientMode = false;
	private IMemoryManager memoryManager = null;
	private EventHandler eventHandler = null;

	// buffer size
	private int minNetBufferSize = 0;
	private int minEncryptedBufferSize = 0;
	
	private ByteBuffer unprocessedEncryptedData = null; 

	
	/**
	 * constructor 
	 * 
	 * @param sslContext    the ssl context
	 * @param isClientMode  true, is ssl processor runs in client mode
	 */
	SSLProcessor(SSLContext sslContext, boolean isClientMode, IMemoryManager memoryManager, EventHandler eventHandler) {
		this.isClientMode = isClientMode;
		this.memoryManager = memoryManager;
		this.eventHandler = eventHandler;
		
		sslEngine = sslContext.createSSLEngine();
		minEncryptedBufferSize = sslEngine.getSession().getApplicationBufferSize();
		minNetBufferSize = sslEngine.getSession().getPacketBufferSize();
		
		if (LOG.isLoggable(Level.FINE)) {
			if (isClientMode) {
				LOG.fine("initializing ssl processor (client mode)");
			} else {
				LOG.fine("initializing ssl processor (server mode)");
			}
			LOG.fine("app buffer size is " + minEncryptedBufferSize);
			LOG.fine("packet buffer size is " + minNetBufferSize);
		}
		
		
		sslEngine.setUseClientMode(isClientMode);
	}
	
	
	
	/**
	 * start ssl processing 
	 * 
	 * @param  inNetData  already received net data
	 * @throws IOException If some other I/O error occurs
	 */
	synchronized void start() throws IOException {
		try {
			sslEngine.beginHandshake();
		} catch (SSLException sslEx) {
			throw new RuntimeException(sslEx);
		}

		
		if (isClientMode) {
 			if (LOG.isLoggable(Level.FINE)) {
 				LOG.fine("initate ssl handshake");
 			}
 			encrypt();
		}
	}


	/**
	 * destroy this instance
	 *
	 */
	void destroy() {
		sslEngine.closeOutbound();
	}
	

	/**
	 * decrypt  data
	 *  
	 * @param encryptedBufferList   the encrypted data 
	 * @throws IOException if an io exction occurs 
	 * @throws ClosedChannelException if the connection is already closed
	 */
	synchronized void decrypt(ByteBuffer[] encryptedBufferList) throws IOException ,ClosedChannelException {

		if (LOG.isLoggable(Level.FINEST)) {
			LOG.finest("decrypting " + encryptedBufferList.length + " buffers");
		}
		
		ArrayList decryptedDataList = new ArrayList(1);
		
		try {
			for (int i = 0; i < encryptedBufferList.length; i++) {
				if (LOG.isLoggable(Level.FINEST)) {
					LOG.finest("processing " + i + ".buffer (encrypted size " + encryptedBufferList[i].remaining() + ")");
				}
				List inAppData = unwrap(encryptedBufferList[i]);
				decryptedDataList.addAll(inAppData);
				
				 
				if (LOG.isLoggable(Level.FINE)) {
					int decryptedDataSize = 0;
					ByteBuffer[] decryptedDataListCopy = new ByteBuffer[decryptedDataList.size()];
					for (int j = 0; j < decryptedDataList.size(); j++) {
						decryptedDataSize += decryptedDataList.get(j).remaining();
						decryptedDataListCopy[j] = decryptedDataList.get(j).duplicate();
					}
					
					if (decryptedDataSize > 0) {
		 				LOG.fine(decryptedDataSize + " decrypted data: " + DataConverter.toTextOrHexString(decryptedDataListCopy, "UTF-8", 500));
					} 
				}
			}
			
			if (!decryptedDataList.isEmpty()) {
				eventHandler.onDataDecrypted(decryptedDataList.toArray(new ByteBuffer[decryptedDataList.size()]));
			}
		} catch (SSLException sslEx) {
			eventHandler.onSSLProcessorClosed();
		}
		
					
		if (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
			encrypt();
		}
	}

	
	/**
	 * return true, if the connection is handshaking
	 * 
	 * @return true, if the connection is handshaking
	 */
	synchronized boolean isHandshaking() {
		HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
		return !(handshakeStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) 
		        || (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED);
	}
	
	
	/**
	 * encrypt data
	 *  
	 * @param plainBuffer  the plain data to encrypt 
	 * @throws IOException if an io exction occurs 
	 * @throws ClosedChannelException if the connection is already closed
	 */
	void encrypt(ByteBuffer[] plainBufferList) throws ClosedChannelException, IOException{
		for (ByteBuffer plainBuffer : plainBufferList) {
			wrap(plainBuffer);
		}
	}

	/**
	 * encrypt data
	 *   
	 * @throws IOException if an io exction occurs 
	 * @throws ClosedChannelException if the connection is already closed
	 */
	void encrypt() throws ClosedChannelException, IOException{
		ByteBuffer outAppData = ByteBuffer.allocate(0); 
		wrap(outAppData);
	}

	
	private synchronized ArrayList unwrap(ByteBuffer inNetData) throws SSLException, ClosedChannelException, IOException {
		ArrayList inAppDataList = new ArrayList(1);
		int minAppSize = minEncryptedBufferSize;
		
		
		if (unprocessedEncryptedData != null) {
			if (LOG.isLoggable(Level.FINEST)) {
				LOG.finest("unprocessed encrypted data available. merging unprocessed encrypted data (size " 
						  + unprocessedEncryptedData.remaining() + ") with " + " new encrypted data (size " 
						  + inNetData.remaining() + ")");
			}
			
			inNetData = mergeBuffer(unprocessedEncryptedData, inNetData);
			unprocessedEncryptedData = null;

			if (LOG.isLoggable(Level.FINEST)) {
				LOG.finest("new encrypted data size is " + inNetData.remaining());
			}
		}
		
		
		try {
			do {
				
				// perform unwrap
				ByteBuffer inAppData = memoryManager.acquireMemoryMinSize(minAppSize);
				SSLEngineResult engineResult = sslEngine.unwrap(inNetData, inAppData);


				if (engineResult.getStatus() == SSLEngineResult.Status.OK) {
						
					// exctract remaining encrypted inNetData
					if (inNetData.position() < inNetData.limit()) {
						inNetData = inNetData.slice();
					}
					
					
								
					// extract unwrapped app data
					inAppData = memoryManager.extractAndRecycleMemory(inAppData, engineResult.bytesProduced());
					if (inAppData.remaining() > 0) {
						inAppDataList.add(inAppData);
						if (LOG.isLoggable(Level.FINEST)) {
							LOG.finest(inAppData.remaining() + " plain data has decrypted");
						}		
						
					} else {
						if (LOG.isLoggable(Level.FINEST)) {
							LOG.finest("SSL System data (InNetData doesn't contain app data)");
						}		
					}
					
					
				} else if (engineResult.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
					/*
					 * There is not enough data on the input buffer to perform the operation. The application should read
					 * more data from the network. If the input buffer does not contain a full packet BufferUnderflow occurs
					 * (unwrap() can only operate on full packets)
					 */
					
					if (LOG.isLoggable(Level.FINEST)) {
						LOG.finest("BufferUnderflow occured (not enough InNet data)");
					}	
					
					unprocessedEncryptedData = inNetData;
					break; 

					
					
				} else if (engineResult.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
					// hmmm? shouldn`t occure, but handle it by expanding the min buffer size
					memoryManager.recycleMemory(inAppData);
					minAppSize += minAppSize;
					continue;
				
					
				} else if (engineResult.getStatus() == SSLEngineResult.Status.CLOSED) {
					memoryManager.recycleMemory(inAppData);
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("ssl engine closed");
					}
				}  
				

								
				// need task?
				if (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
					Runnable task = null;
					while ((task = sslEngine.getDelegatedTask()) != null) {
					    task.run();
					}
				}
				

				// finished ?
				if (engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) {
					notifyHandshakeFinished();
				}
				
				
			} while (inNetData.hasRemaining());
			
		} catch (SSLException ssles) {
			throw ssles;
		}

		return inAppDataList;
	}
	
	
	
	private synchronized void wrap(ByteBuffer outAppData) throws SSLException, ClosedChannelException, IOException {
		int minNetSize = minNetBufferSize;

		do {

			// perform wrap
			ByteBuffer outNetData = memoryManager.acquireMemoryMinSize(minNetSize);
			SSLEngineResult engineResult = sslEngine.wrap(outAppData, outNetData);
			
			
			// handle the engine result
		    if ((engineResult.getStatus() == SSLEngineResult.Status.OK) 
		    	|| (engineResult.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW)) {

				// extract encrypted net data
				outNetData = memoryManager.extractAndRecycleMemory(outNetData, engineResult.bytesProduced());
				if (outNetData.hasRemaining()) {
					eventHandler.onDataEncrypted(outAppData, outNetData);
				}

				
		    } else if (engineResult.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
				// hmmm? shouldn`t occure, but handle it by expanding the min buffer size
				memoryManager.recycleMemory(outNetData);
				minNetSize += minNetSize;
				continue;

				
			} else if (engineResult.getStatus() == SSLEngineResult.Status.CLOSED) {
				memoryManager.recycleMemory(outNetData);
				throw new ClosedChannelException();
			}
		    
		    
			// need task?
			if (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
				Runnable task = null;
				while ((task = sslEngine.getDelegatedTask()) != null) {
				    task.run();
				}
			}

			
			// finished ?
			if (engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) {
				notifyHandshakeFinished();
				break;
			}
		} while (outAppData.hasRemaining() || (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP));
		
	}
	
	
	private void notifyHandshakeFinished() throws IOException {
		if (LOG.isLoggable(Level.FINE)) {
			if (isClientMode) {
				LOG.fine("handshake has been finished (clientMode)");
			} else {
				LOG.fine("handshake has been finished (serverMode)");
			}
		}
		
		encrypt();
		eventHandler.onHandshakeFinished();	
	}
	
	

	private ByteBuffer mergeBuffer(ByteBuffer first, ByteBuffer second) {
		ByteBuffer mergedBuffer = ByteBuffer.allocate(first.remaining() + second.remaining());
		mergedBuffer.put(first);
		mergedBuffer.put(second);
		mergedBuffer.flip();
		
		return mergedBuffer;
	}
	

	
	/**
	 * SSLProcessor call back interface
	 * 
	 * @author grro
	 */
	static interface EventHandler {

		/**
		 * signals that the handshake has been finished
		 * 
	     * @throws IOException if an io exception occurs
		 */
		public void onHandshakeFinished() throws IOException;
		
		
		/**
		 * singals data has been decrypted
		 * 
		 * @param decryptedBufferList the decrypted data
		 */
		public void onDataDecrypted(ByteBuffer[] decryptedBufferList);
		
		
		/**
		 * signals that data has been encrypted
		 * 
		 * @param plainData      the plain data
		 * @param encryptedData  the encrypted data
	     * @throws IOException if an io exception occurs
		 */
		public void onDataEncrypted(ByteBuffer plainData, ByteBuffer encryptedData) throws IOException ;
		
		
		/**
		 * signals, that the SSLProcessor has been closed 
		 * 
	     * @throws IOException if an io exception occurs
		 */
		public void onSSLProcessorClosed() throws IOException;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy