001: /*
002: * Copyright (c) xsocket.org, 2006 - 2008. All rights reserved.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: * Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
019: * The latest copy of this software may be found on http://www.xsocket.org/
020: */
021: package org.xsocket.connection.spi;
022:
023: import java.io.IOException;
024: import java.nio.ByteBuffer;
025: import java.nio.channels.ClosedChannelException;
026: import java.util.ArrayList;
027: import java.util.List;
028: import java.util.logging.Level;
029: import java.util.logging.Logger;
030:
031: import javax.net.ssl.SSLContext;
032: import javax.net.ssl.SSLEngine;
033: import javax.net.ssl.SSLEngineResult;
034: import javax.net.ssl.SSLException;
035: import javax.net.ssl.SSLEngineResult.HandshakeStatus;
036:
037: import org.xsocket.DataConverter;
038:
039: /**
040: * ssl processor
041: *
042: * @author grro@xsocket.org
043: */
044: final class SSLProcessor {
045:
046: private static final Logger LOG = Logger
047: .getLogger(SSLProcessor.class.getName());
048:
049: private SSLEngine sslEngine = null;
050:
051: private boolean isClientMode = false;
052: private IMemoryManager memoryManager = null;
053: private EventHandler eventHandler = null;
054:
055: // buffer size
056: private int minNetBufferSize = 0;
057: private int minEncryptedBufferSize = 0;
058:
059: private ByteBuffer unprocessedEncryptedData = null;
060:
061: /**
062: * constructor
063: *
064: * @param sslContext the ssl context
065: * @param isClientMode true, is ssl processor runs in client mode
066: */
067: SSLProcessor(SSLContext sslContext, boolean isClientMode,
068: IMemoryManager memoryManager, EventHandler eventHandler) {
069: this .isClientMode = isClientMode;
070: this .memoryManager = memoryManager;
071: this .eventHandler = eventHandler;
072:
073: sslEngine = sslContext.createSSLEngine();
074: minEncryptedBufferSize = sslEngine.getSession()
075: .getApplicationBufferSize();
076: minNetBufferSize = sslEngine.getSession().getPacketBufferSize();
077:
078: if (LOG.isLoggable(Level.FINE)) {
079: if (isClientMode) {
080: LOG.fine("initializing ssl processor (client mode)");
081: } else {
082: LOG.fine("initializing ssl processor (server mode)");
083: }
084: LOG.fine("app buffer size is " + minEncryptedBufferSize);
085: LOG.fine("packet buffer size is " + minNetBufferSize);
086: }
087:
088: sslEngine.setUseClientMode(isClientMode);
089: }
090:
091: /**
092: * start ssl processing
093: *
094: * @param inNetData already received net data
095: * @throws IOException If some other I/O error occurs
096: */
097: synchronized void start() throws IOException {
098: try {
099: sslEngine.beginHandshake();
100: } catch (SSLException sslEx) {
101: throw new RuntimeException(sslEx);
102: }
103:
104: if (isClientMode) {
105: if (LOG.isLoggable(Level.FINE)) {
106: LOG.fine("initate ssl handshake");
107: }
108: encrypt();
109: }
110: }
111:
112: /**
113: * destroy this instance
114: *
115: */
116: void destroy() {
117: sslEngine.closeOutbound();
118: }
119:
120: /**
121: * decrypt data
122: *
123: * @param encryptedBufferList the encrypted data
124: * @throws IOException if an io exction occurs
125: * @throws ClosedChannelException if the connection is already closed
126: */
127: synchronized void decrypt(ByteBuffer[] encryptedBufferList)
128: throws IOException, ClosedChannelException {
129:
130: if (LOG.isLoggable(Level.FINEST)) {
131: LOG.finest("decrypting " + encryptedBufferList.length
132: + " buffers");
133: }
134:
135: ArrayList<ByteBuffer> decryptedDataList = new ArrayList<ByteBuffer>(
136: 1);
137:
138: try {
139: for (int i = 0; i < encryptedBufferList.length; i++) {
140: if (LOG.isLoggable(Level.FINEST)) {
141: LOG.finest("processing " + i
142: + ".buffer (encrypted size "
143: + encryptedBufferList[i].remaining() + ")");
144: }
145: List<ByteBuffer> inAppData = unwrap(encryptedBufferList[i]);
146: decryptedDataList.addAll(inAppData);
147:
148: if (LOG.isLoggable(Level.FINE)) {
149: int decryptedDataSize = 0;
150: ByteBuffer[] decryptedDataListCopy = new ByteBuffer[decryptedDataList
151: .size()];
152: for (int j = 0; j < decryptedDataList.size(); j++) {
153: decryptedDataSize += decryptedDataList.get(j)
154: .remaining();
155: decryptedDataListCopy[j] = decryptedDataList
156: .get(j).duplicate();
157: }
158:
159: if (decryptedDataSize > 0) {
160: LOG.fine(decryptedDataSize
161: + " decrypted data: "
162: + DataConverter.toTextOrHexString(
163: decryptedDataListCopy, "UTF-8",
164: 500));
165: }
166: }
167: }
168:
169: if (!decryptedDataList.isEmpty()) {
170: eventHandler.onDataDecrypted(decryptedDataList
171: .toArray(new ByteBuffer[decryptedDataList
172: .size()]));
173: }
174: } catch (SSLException sslEx) {
175: eventHandler.onSSLProcessorClosed();
176: }
177:
178: if (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
179: encrypt();
180: }
181: }
182:
183: /**
184: * return true, if the connection is handshaking
185: *
186: * @return true, if the connection is handshaking
187: */
188: synchronized boolean isHandshaking() {
189: HandshakeStatus handshakeStatus = sslEngine
190: .getHandshakeStatus();
191: return !(handshakeStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
192: || (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED);
193: }
194:
195: /**
196: * encrypt data
197: *
198: * @param plainBuffer the plain data to encrypt
199: * @throws IOException if an io exction occurs
200: * @throws ClosedChannelException if the connection is already closed
201: */
202: void encrypt(ByteBuffer[] plainBufferList)
203: throws ClosedChannelException, IOException {
204: for (ByteBuffer plainBuffer : plainBufferList) {
205: wrap(plainBuffer);
206: }
207: }
208:
209: /**
210: * encrypt data
211: *
212: * @throws IOException if an io exction occurs
213: * @throws ClosedChannelException if the connection is already closed
214: */
215: void encrypt() throws ClosedChannelException, IOException {
216: ByteBuffer outAppData = ByteBuffer.allocate(0);
217: wrap(outAppData);
218: }
219:
220: private synchronized ArrayList<ByteBuffer> unwrap(
221: ByteBuffer inNetData) throws SSLException,
222: ClosedChannelException, IOException {
223: ArrayList<ByteBuffer> inAppDataList = new ArrayList<ByteBuffer>(
224: 1);
225: int minAppSize = minEncryptedBufferSize;
226:
227: if (unprocessedEncryptedData != null) {
228: if (LOG.isLoggable(Level.FINEST)) {
229: LOG
230: .finest("unprocessed encrypted data available. merging unprocessed encrypted data (size "
231: + unprocessedEncryptedData.remaining()
232: + ") with "
233: + " new encrypted data (size "
234: + inNetData.remaining() + ")");
235: }
236:
237: inNetData = mergeBuffer(unprocessedEncryptedData, inNetData);
238: unprocessedEncryptedData = null;
239:
240: if (LOG.isLoggable(Level.FINEST)) {
241: LOG.finest("new encrypted data size is "
242: + inNetData.remaining());
243: }
244: }
245:
246: try {
247: do {
248:
249: // perform unwrap
250: ByteBuffer inAppData = memoryManager
251: .acquireMemoryMinSize(minAppSize);
252: SSLEngineResult engineResult = sslEngine.unwrap(
253: inNetData, inAppData);
254:
255: if (engineResult.getStatus() == SSLEngineResult.Status.OK) {
256:
257: // exctract remaining encrypted inNetData
258: if (inNetData.position() < inNetData.limit()) {
259: inNetData = inNetData.slice();
260: }
261:
262: // extract unwrapped app data
263: inAppData = memoryManager.extractAndRecycleMemory(
264: inAppData, engineResult.bytesProduced());
265: if (inAppData.remaining() > 0) {
266: inAppDataList.add(inAppData);
267: if (LOG.isLoggable(Level.FINEST)) {
268: LOG.finest(inAppData.remaining()
269: + " plain data has decrypted");
270: }
271:
272: } else {
273: if (LOG.isLoggable(Level.FINEST)) {
274: LOG
275: .finest("SSL System data (InNetData doesn't contain app data)");
276: }
277: }
278:
279: } else if (engineResult.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
280: /*
281: * There is not enough data on the input buffer to perform the operation. The application should read
282: * more data from the network. If the input buffer does not contain a full packet BufferUnderflow occurs
283: * (unwrap() can only operate on full packets)
284: */
285:
286: if (LOG.isLoggable(Level.FINEST)) {
287: LOG
288: .finest("BufferUnderflow occured (not enough InNet data)");
289: }
290:
291: unprocessedEncryptedData = inNetData;
292: break;
293:
294: } else if (engineResult.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
295: // hmmm? shouldn`t occure, but handle it by expanding the min buffer size
296: memoryManager.recycleMemory(inAppData);
297: minAppSize += minAppSize;
298: continue;
299:
300: } else if (engineResult.getStatus() == SSLEngineResult.Status.CLOSED) {
301: memoryManager.recycleMemory(inAppData);
302: if (LOG.isLoggable(Level.FINE)) {
303: LOG.fine("ssl engine closed");
304: }
305: }
306:
307: // need task?
308: if (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
309: Runnable task = null;
310: while ((task = sslEngine.getDelegatedTask()) != null) {
311: task.run();
312: }
313: }
314:
315: // finished ?
316: if (engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) {
317: notifyHandshakeFinished();
318: }
319:
320: } while (inNetData.hasRemaining());
321:
322: } catch (SSLException ssles) {
323: throw ssles;
324: }
325:
326: return inAppDataList;
327: }
328:
329: private synchronized void wrap(ByteBuffer outAppData)
330: throws SSLException, ClosedChannelException, IOException {
331: int minNetSize = minNetBufferSize;
332:
333: do {
334:
335: // perform wrap
336: ByteBuffer outNetData = memoryManager
337: .acquireMemoryMinSize(minNetSize);
338: SSLEngineResult engineResult = sslEngine.wrap(outAppData,
339: outNetData);
340:
341: // handle the engine result
342: if ((engineResult.getStatus() == SSLEngineResult.Status.OK)
343: || (engineResult.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW)) {
344:
345: // extract encrypted net data
346: outNetData = memoryManager.extractAndRecycleMemory(
347: outNetData, engineResult.bytesProduced());
348: if (outNetData.hasRemaining()) {
349: eventHandler
350: .onDataEncrypted(outAppData, outNetData);
351: }
352:
353: } else if (engineResult.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
354: // hmmm? shouldn`t occure, but handle it by expanding the min buffer size
355: memoryManager.recycleMemory(outNetData);
356: minNetSize += minNetSize;
357: continue;
358:
359: } else if (engineResult.getStatus() == SSLEngineResult.Status.CLOSED) {
360: memoryManager.recycleMemory(outNetData);
361: throw new ClosedChannelException();
362: }
363:
364: // need task?
365: if (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
366: Runnable task = null;
367: while ((task = sslEngine.getDelegatedTask()) != null) {
368: task.run();
369: }
370: }
371:
372: // finished ?
373: if (engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) {
374: notifyHandshakeFinished();
375: break;
376: }
377: } while (outAppData.hasRemaining()
378: || (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP));
379:
380: }
381:
382: private void notifyHandshakeFinished() throws IOException {
383: if (LOG.isLoggable(Level.FINE)) {
384: if (isClientMode) {
385: LOG.fine("handshake has been finished (clientMode)");
386: } else {
387: LOG.fine("handshake has been finished (serverMode)");
388: }
389: }
390:
391: encrypt();
392: eventHandler.onHandshakeFinished();
393: }
394:
395: private ByteBuffer mergeBuffer(ByteBuffer first, ByteBuffer second) {
396: ByteBuffer mergedBuffer = ByteBuffer.allocate(first.remaining()
397: + second.remaining());
398: mergedBuffer.put(first);
399: mergedBuffer.put(second);
400: mergedBuffer.flip();
401:
402: return mergedBuffer;
403: }
404:
405: /**
406: * SSLProcessor call back interface
407: *
408: * @author grro
409: */
410: static interface EventHandler {
411:
412: /**
413: * signals that the handshake has been finished
414: *
415: * @throws IOException if an io exception occurs
416: */
417: public void onHandshakeFinished() throws IOException;
418:
419: /**
420: * singals data has been decrypted
421: *
422: * @param decryptedBufferList the decrypted data
423: */
424: public void onDataDecrypted(ByteBuffer[] decryptedBufferList);
425:
426: /**
427: * signals that data has been encrypted
428: *
429: * @param plainData the plain data
430: * @param encryptedData the encrypted data
431: * @throws IOException if an io exception occurs
432: */
433: public void onDataEncrypted(ByteBuffer plainData,
434: ByteBuffer encryptedData) throws IOException;
435:
436: /**
437: * signals, that the SSLProcessor has been closed
438: *
439: * @throws IOException if an io exception occurs
440: */
441: public void onSSLProcessorClosed() throws IOException;
442: }
443: }
|