001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.apache.mina.filter.ssl;
021:
022: import java.net.InetSocketAddress;
023: import java.nio.ByteBuffer;
024: import java.util.Queue;
025: import java.util.concurrent.ConcurrentLinkedQueue;
026:
027: import javax.net.ssl.SSLContext;
028: import javax.net.ssl.SSLEngine;
029: import javax.net.ssl.SSLEngineResult;
030: import javax.net.ssl.SSLException;
031: import javax.net.ssl.SSLHandshakeException;
032:
033: import org.apache.mina.common.DefaultWriteFuture;
034: import org.apache.mina.common.DefaultWriteRequest;
035: import org.apache.mina.common.IoBuffer;
036: import org.apache.mina.common.IoEventType;
037: import org.apache.mina.common.IoFilterEvent;
038: import org.apache.mina.common.IoSession;
039: import org.apache.mina.common.WriteFuture;
040: import org.apache.mina.common.WriteRequest;
041: import org.apache.mina.common.IoFilter.NextFilter;
042: import org.apache.mina.util.CircularQueue;
043: import org.slf4j.Logger;
044: import org.slf4j.LoggerFactory;
045:
046: /**
047: * A helper class using the SSLEngine API to decrypt/encrypt data.
048: * <p/>
049: * Each connection has a SSLEngine that is used through the lifetime of the connection.
050: * We allocate buffers for use as the outbound and inbound network buffers.
051: * These buffers handle all of the intermediary data for the SSL connection. To make things easy,
052: * we'll require outNetBuffer be completely flushed before trying to wrap any more data.
053: *
054: * @author The Apache MINA Project (dev@mina.apache.org)
055: * @version $Rev: 616100 $, $Date: 2008-01-28 15:58:32 -0700 (Mon, 28 Jan 2008) $
056: */
057: class SslHandler {
058:
059: private final Logger logger = LoggerFactory.getLogger(getClass());
060: private final SslFilter parent;
061: private final SSLContext ctx;
062: private final IoSession session;
063: private final Queue<IoFilterEvent> preHandshakeEventQueue = new CircularQueue<IoFilterEvent>();
064: private final Queue<IoFilterEvent> filterWriteEventQueue = new ConcurrentLinkedQueue<IoFilterEvent>();
065: private final Queue<IoFilterEvent> messageReceivedEventQueue = new ConcurrentLinkedQueue<IoFilterEvent>();
066: private SSLEngine sslEngine;
067:
068: /**
069: * Encrypted data from the net
070: */
071: private IoBuffer inNetBuffer;
072:
073: /**
074: * Encrypted data to be written to the net
075: */
076: private IoBuffer outNetBuffer;
077:
078: /**
079: * Applicaton cleartext data to be read by application
080: */
081: private IoBuffer appBuffer;
082:
083: /**
084: * Empty buffer used during initial handshake and close operations
085: */
086: private final IoBuffer emptyBuffer = IoBuffer.allocate(0);
087:
088: private SSLEngineResult.HandshakeStatus handshakeStatus;
089: private boolean initialHandshakeComplete;
090: private boolean handshakeComplete;
091: private boolean writingEncryptedData;
092:
093: /**
094: * Constuctor.
095: *
096: * @param sslc
097: * @throws SSLException
098: */
099: public SslHandler(SslFilter parent, SSLContext sslc,
100: IoSession session) throws SSLException {
101: this .parent = parent;
102: this .session = session;
103: this .ctx = sslc;
104: init();
105: }
106:
107: public void init() throws SSLException {
108: if (sslEngine != null) {
109: return;
110: }
111:
112: InetSocketAddress peer = (InetSocketAddress) session
113: .getAttribute(SslFilter.PEER_ADDRESS);
114: if (peer == null) {
115: sslEngine = ctx.createSSLEngine();
116: } else {
117: sslEngine = ctx.createSSLEngine(peer.getHostName(), peer
118: .getPort());
119: }
120: sslEngine.setUseClientMode(parent.isUseClientMode());
121:
122: if (parent.isWantClientAuth()) {
123: sslEngine.setWantClientAuth(true);
124: }
125:
126: if (parent.isNeedClientAuth()) {
127: sslEngine.setNeedClientAuth(true);
128: }
129:
130: if (parent.getEnabledCipherSuites() != null) {
131: sslEngine.setEnabledCipherSuites(parent
132: .getEnabledCipherSuites());
133: }
134:
135: if (parent.getEnabledProtocols() != null) {
136: sslEngine.setEnabledProtocols(parent.getEnabledProtocols());
137: }
138:
139: sslEngine.beginHandshake();
140: handshakeStatus = sslEngine.getHandshakeStatus();
141:
142: handshakeComplete = false;
143: initialHandshakeComplete = false;
144: writingEncryptedData = false;
145: }
146:
147: /**
148: * Release allocated buffers.
149: */
150: public void destroy() {
151: if (sslEngine == null) {
152: return;
153: }
154:
155: // Close inbound and flush all remaining data if available.
156: try {
157: sslEngine.closeInbound();
158: } catch (SSLException e) {
159: logger
160: .debug(
161: "Unexpected exception from SSLEngine.closeInbound().",
162: e);
163: }
164:
165: if (outNetBuffer != null) {
166: outNetBuffer.capacity(sslEngine.getSession()
167: .getPacketBufferSize());
168: } else {
169: createOutNetBuffer(0);
170: }
171: try {
172: do {
173: outNetBuffer.clear();
174: } while (sslEngine.wrap(emptyBuffer.buf(),
175: outNetBuffer.buf()).bytesProduced() > 0);
176: } catch (SSLException e) {
177: // Ignore.
178: } finally {
179: destroyOutNetBuffer();
180: }
181:
182: sslEngine.closeOutbound();
183: sslEngine = null;
184:
185: preHandshakeEventQueue.clear();
186: }
187:
188: private void destroyOutNetBuffer() {
189: outNetBuffer.free();
190: outNetBuffer = null;
191: }
192:
193: public SslFilter getParent() {
194: return parent;
195: }
196:
197: public IoSession getSession() {
198: return session;
199: }
200:
201: /**
202: * Check we are writing encrypted data.
203: */
204: public boolean isWritingEncryptedData() {
205: return writingEncryptedData;
206: }
207:
208: /**
209: * Check if handshake is completed.
210: */
211: public boolean isHandshakeComplete() {
212: return handshakeComplete;
213: }
214:
215: public boolean isInboundDone() {
216: return sslEngine == null || sslEngine.isInboundDone();
217: }
218:
219: public boolean isOutboundDone() {
220: return sslEngine == null || sslEngine.isOutboundDone();
221: }
222:
223: /**
224: * Check if there is any need to complete handshake.
225: */
226: public boolean needToCompleteHandshake() {
227: return handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
228: && !isInboundDone();
229: }
230:
231: public void schedulePreHandshakeWriteRequest(NextFilter nextFilter,
232: WriteRequest writeRequest) {
233: preHandshakeEventQueue.add(new IoFilterEvent(nextFilter,
234: IoEventType.WRITE, session, writeRequest));
235: }
236:
237: public void flushPreHandshakeEvents() throws SSLException {
238: IoFilterEvent scheduledWrite;
239:
240: while ((scheduledWrite = preHandshakeEventQueue.poll()) != null) {
241: parent.filterWrite(scheduledWrite.getNextFilter(), session,
242: (WriteRequest) scheduledWrite.getParameter());
243: }
244: }
245:
246: public void scheduleFilterWrite(NextFilter nextFilter,
247: WriteRequest writeRequest) {
248: filterWriteEventQueue.add(new IoFilterEvent(nextFilter,
249: IoEventType.WRITE, session, writeRequest));
250: }
251:
252: public void scheduleMessageReceived(NextFilter nextFilter,
253: Object message) {
254: messageReceivedEventQueue.add(new IoFilterEvent(nextFilter,
255: IoEventType.MESSAGE_RECEIVED, session, message));
256: }
257:
258: public void flushScheduledEvents() {
259: // Fire events only when no lock is hold for this handler.
260: if (Thread.holdsLock(this )) {
261: return;
262: }
263:
264: IoFilterEvent e;
265:
266: // We need synchronization here inevitably because filterWrite can be
267: // called simultaneously and cause 'bad record MAC' integrity error.
268: synchronized (this ) {
269: while ((e = filterWriteEventQueue.poll()) != null) {
270: e.getNextFilter().filterWrite(session,
271: (WriteRequest) e.getParameter());
272: }
273: }
274:
275: while ((e = messageReceivedEventQueue.poll()) != null) {
276: e.getNextFilter()
277: .messageReceived(session, e.getParameter());
278: }
279: }
280:
281: /**
282: * Call when data read from net. Will perform inial hanshake or decrypt provided
283: * Buffer.
284: * Decrytpted data reurned by getAppBuffer(), if any.
285: *
286: * @param buf buffer to decrypt
287: * @param nextFilter Next filter in chain
288: * @throws SSLException on errors
289: */
290: public void messageReceived(NextFilter nextFilter, ByteBuffer buf)
291: throws SSLException {
292: // append buf to inNetBuffer
293: if (inNetBuffer == null) {
294: inNetBuffer = IoBuffer.allocate(buf.remaining())
295: .setAutoExpand(true);
296: }
297:
298: inNetBuffer.put(buf);
299: if (!handshakeComplete) {
300: handshake(nextFilter);
301: } else {
302: decrypt(nextFilter);
303: }
304:
305: if (isInboundDone()) {
306: // Rewind the MINA buffer if not all data is processed and inbound is finished.
307: int inNetBufferPosition = inNetBuffer == null ? 0
308: : inNetBuffer.position();
309: buf.position(buf.position() - inNetBufferPosition);
310: inNetBuffer = null;
311: }
312: }
313:
314: /**
315: * Get decrypted application data.
316: *
317: * @return buffer with data
318: */
319: public IoBuffer fetchAppBuffer() {
320: IoBuffer appBuffer = this .appBuffer.flip();
321: this .appBuffer = null;
322: return appBuffer;
323: }
324:
325: /**
326: * Get encrypted data to be sent.
327: *
328: * @return buffer with data
329: */
330: public IoBuffer fetchOutNetBuffer() {
331: IoBuffer answer = outNetBuffer;
332: if (answer == null) {
333: return emptyBuffer;
334: }
335:
336: outNetBuffer = null;
337: return answer.shrink();
338: }
339:
340: /**
341: * Encrypt provided buffer. Encytpted data reurned by getOutNetBuffer().
342: *
343: * @param src data to encrypt
344: * @throws SSLException on errors
345: */
346: public void encrypt(ByteBuffer src) throws SSLException {
347: if (!handshakeComplete) {
348: throw new IllegalStateException();
349: }
350:
351: if (!src.hasRemaining()) {
352: if (outNetBuffer == null) {
353: outNetBuffer = emptyBuffer;
354: }
355: return;
356: }
357:
358: createOutNetBuffer(src.remaining());
359:
360: // Loop until there is no more data in src
361: while (src.hasRemaining()) {
362:
363: SSLEngineResult result = sslEngine.wrap(src, outNetBuffer
364: .buf());
365: if (result.getStatus() == SSLEngineResult.Status.OK) {
366: if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
367: doTasks();
368: }
369: } else if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
370: outNetBuffer.capacity(outNetBuffer.capacity() << 1);
371: outNetBuffer.limit(outNetBuffer.capacity());
372: } else {
373: throw new SSLException(
374: "SSLEngine error during encrypt: "
375: + result.getStatus() + " src: " + src
376: + "outNetBuffer: " + outNetBuffer);
377: }
378: }
379:
380: outNetBuffer.flip();
381: }
382:
383: /**
384: * Start SSL shutdown process.
385: *
386: * @return <tt>true</tt> if shutdown process is started.
387: * <tt>false</tt> if shutdown process is already finished.
388: * @throws SSLException on errors
389: */
390: public boolean closeOutbound() throws SSLException {
391: if (sslEngine == null || sslEngine.isOutboundDone()) {
392: return false;
393: }
394:
395: sslEngine.closeOutbound();
396:
397: createOutNetBuffer(0);
398: SSLEngineResult result;
399: for (;;) {
400: result = sslEngine.wrap(emptyBuffer.buf(), outNetBuffer
401: .buf());
402: if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
403: outNetBuffer.capacity(outNetBuffer.capacity() << 1);
404: outNetBuffer.limit(outNetBuffer.capacity());
405: } else {
406: break;
407: }
408: }
409:
410: if (result.getStatus() != SSLEngineResult.Status.CLOSED) {
411: throw new SSLException("Improper close state: " + result);
412: }
413: outNetBuffer.flip();
414: return true;
415: }
416:
417: /**
418: * Decrypt in net buffer. Result is stored in app buffer.
419: *
420: * @throws SSLException
421: */
422: private void decrypt(NextFilter nextFilter) throws SSLException {
423:
424: if (!handshakeComplete) {
425: throw new IllegalStateException();
426: }
427:
428: unwrap(nextFilter);
429: }
430:
431: /**
432: * @param res
433: * @throws SSLException
434: */
435: private void checkStatus(SSLEngineResult res) throws SSLException {
436:
437: SSLEngineResult.Status status = res.getStatus();
438:
439: /*
440: * The status may be:
441: * OK - Normal operation
442: * OVERFLOW - Should never happen since the application buffer is
443: * sized to hold the maximum packet size.
444: * UNDERFLOW - Need to read more data from the socket. It's normal.
445: * CLOSED - The other peer closed the socket. Also normal.
446: */
447: if (status != SSLEngineResult.Status.OK
448: && status != SSLEngineResult.Status.CLOSED
449: && status != SSLEngineResult.Status.BUFFER_UNDERFLOW) {
450: throw new SSLException("SSLEngine error during decrypt: "
451: + status + " inNetBuffer: " + inNetBuffer
452: + "appBuffer: " + appBuffer);
453: }
454: }
455:
456: /**
457: * Perform any handshaking processing.
458: */
459: public void handshake(NextFilter nextFilter) throws SSLException {
460: for (;;) {
461: if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED) {
462: session.setAttribute(SslFilter.SSL_SESSION, sslEngine
463: .getSession());
464: handshakeComplete = true;
465: if (!initialHandshakeComplete
466: && session
467: .containsAttribute(SslFilter.USE_NOTIFICATION)) {
468: // SESSION_SECURED is fired only when it's the first handshake.
469: // (i.e. renegotiation shouldn't trigger SESSION_SECURED.)
470: initialHandshakeComplete = true;
471: scheduleMessageReceived(nextFilter,
472: SslFilter.SESSION_SECURED);
473: }
474: break;
475: } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) {
476: handshakeStatus = doTasks();
477: } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
478: // we need more data read
479: SSLEngineResult.Status status = unwrapHandshake(nextFilter);
480: if (status == SSLEngineResult.Status.BUFFER_UNDERFLOW
481: || isInboundDone()) {
482: // We need more data or the session is closed
483: break;
484: }
485: } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
486: // First make sure that the out buffer is completely empty. Since we
487: // cannot call wrap with data left on the buffer
488: if (outNetBuffer != null && outNetBuffer.hasRemaining()) {
489: break;
490: }
491:
492: SSLEngineResult result;
493: createOutNetBuffer(0);
494: for (;;) {
495: result = sslEngine.wrap(emptyBuffer.buf(),
496: outNetBuffer.buf());
497: if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
498: outNetBuffer
499: .capacity(outNetBuffer.capacity() << 1);
500: outNetBuffer.limit(outNetBuffer.capacity());
501: } else {
502: break;
503: }
504: }
505:
506: outNetBuffer.flip();
507: handshakeStatus = result.getHandshakeStatus();
508: writeNetBuffer(nextFilter);
509: } else {
510: throw new IllegalStateException(
511: "Invalid Handshaking State" + handshakeStatus);
512: }
513: }
514: }
515:
516: private void createOutNetBuffer(int expectedRemaining) {
517: // SSLEngine requires us to allocate unnecessarily big buffer
518: // even for small data. *Shrug*
519: int capacity = Math.max(expectedRemaining, sslEngine
520: .getSession().getPacketBufferSize());
521:
522: if (outNetBuffer != null) {
523: outNetBuffer.capacity(capacity);
524: } else {
525: outNetBuffer = IoBuffer.allocate(capacity).minimumCapacity(
526: 0);
527: }
528: }
529:
530: public WriteFuture writeNetBuffer(NextFilter nextFilter)
531: throws SSLException {
532: // Check if any net data needed to be writen
533: if (outNetBuffer == null || !outNetBuffer.hasRemaining()) {
534: // no; bail out
535: return null;
536: }
537:
538: // set flag that we are writing encrypted data
539: // (used in SSLFilter.filterWrite())
540: writingEncryptedData = true;
541:
542: // write net data
543: WriteFuture writeFuture = null;
544:
545: try {
546: IoBuffer writeBuffer = fetchOutNetBuffer();
547: writeFuture = new DefaultWriteFuture(session);
548: parent.filterWrite(nextFilter, session,
549: new DefaultWriteRequest(writeBuffer, writeFuture));
550:
551: // loop while more writes required to complete handshake
552: while (needToCompleteHandshake()) {
553: try {
554: handshake(nextFilter);
555: } catch (SSLException ssle) {
556: SSLException newSsle = new SSLHandshakeException(
557: "SSL handshake failed.");
558: newSsle.initCause(ssle);
559: throw newSsle;
560: }
561:
562: IoBuffer outNetBuffer = fetchOutNetBuffer();
563: if (outNetBuffer != null && outNetBuffer.hasRemaining()) {
564: writeFuture = new DefaultWriteFuture(session);
565: parent.filterWrite(nextFilter, session,
566: new DefaultWriteRequest(outNetBuffer,
567: writeFuture));
568: }
569: }
570: } finally {
571: writingEncryptedData = false;
572: }
573:
574: return writeFuture;
575: }
576:
577: private void unwrap(NextFilter nextFilter) throws SSLException {
578: // Prepare the net data for reading.
579: if (inNetBuffer != null) {
580: inNetBuffer.flip();
581: }
582:
583: if (inNetBuffer == null || !inNetBuffer.hasRemaining()) {
584: return;
585: }
586:
587: SSLEngineResult res = unwrap0();
588:
589: // prepare to be written again
590: if (inNetBuffer.hasRemaining()) {
591: inNetBuffer.compact();
592: } else {
593: inNetBuffer = null;
594: }
595:
596: checkStatus(res);
597:
598: renegotiateIfNeeded(nextFilter, res);
599: }
600:
601: private SSLEngineResult.Status unwrapHandshake(NextFilter nextFilter)
602: throws SSLException {
603: // Prepare the net data for reading.
604: if (inNetBuffer != null) {
605: inNetBuffer.flip();
606: }
607:
608: if (inNetBuffer == null || !inNetBuffer.hasRemaining()) {
609: // Need more data.
610: return SSLEngineResult.Status.BUFFER_UNDERFLOW;
611: }
612:
613: SSLEngineResult res = unwrap0();
614: handshakeStatus = res.getHandshakeStatus();
615:
616: checkStatus(res);
617:
618: // If handshake finished, no data was produced, and the status is still ok,
619: // try to unwrap more
620: if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED
621: && res.getStatus() == SSLEngineResult.Status.OK
622: && inNetBuffer.hasRemaining()) {
623: res = unwrap0();
624:
625: // prepare to be written again
626: if (inNetBuffer.hasRemaining()) {
627: inNetBuffer.compact();
628: } else {
629: inNetBuffer = null;
630: }
631:
632: renegotiateIfNeeded(nextFilter, res);
633: } else {
634: // prepare to be written again
635: if (inNetBuffer.hasRemaining()) {
636: inNetBuffer.compact();
637: } else {
638: inNetBuffer = null;
639: }
640: }
641:
642: return res.getStatus();
643: }
644:
645: private void renegotiateIfNeeded(NextFilter nextFilter,
646: SSLEngineResult res) throws SSLException {
647: if (res.getStatus() != SSLEngineResult.Status.CLOSED
648: && res.getStatus() != SSLEngineResult.Status.BUFFER_UNDERFLOW
649: && res.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
650: // Renegotiation required.
651: handshakeComplete = false;
652: handshakeStatus = res.getHandshakeStatus();
653: handshake(nextFilter);
654: }
655: }
656:
657: private SSLEngineResult unwrap0() throws SSLException {
658: if (appBuffer == null) {
659: appBuffer = IoBuffer.allocate(inNetBuffer.remaining());
660: } else {
661: appBuffer.expand(inNetBuffer.remaining());
662: }
663:
664: SSLEngineResult res;
665: do {
666: res = sslEngine.unwrap(inNetBuffer.buf(), appBuffer.buf());
667: if (res.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
668: appBuffer.capacity(appBuffer.capacity() << 1);
669: appBuffer.limit(appBuffer.capacity());
670: continue;
671: }
672: } while ((res.getStatus() == SSLEngineResult.Status.OK || res
673: .getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW)
674: && (handshakeComplete
675: && res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING || res
676: .getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP));
677:
678: return res;
679: }
680:
681: /**
682: * Do all the outstanding handshake tasks in the current Thread.
683: */
684: private SSLEngineResult.HandshakeStatus doTasks() {
685: /*
686: * We could run this in a separate thread, but I don't see the need
687: * for this when used from SSLFilter. Use thread filters in MINA instead?
688: */
689: Runnable runnable;
690: while ((runnable = sslEngine.getDelegatedTask()) != null) {
691: runnable.run();
692: }
693: return sslEngine.getHandshakeStatus();
694: }
695:
696: /**
697: * Creates a new MINA buffer that is a deep copy of the remaining bytes
698: * in the given buffer (between index buf.position() and buf.limit())
699: *
700: * @param src the buffer to copy
701: * @return the new buffer, ready to read from
702: */
703: public static IoBuffer copy(ByteBuffer src) {
704: IoBuffer copy = IoBuffer.allocate(src.remaining());
705: copy.put(src);
706: copy.flip();
707: return copy;
708: }
709: }
|