1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.filter.support;
21
22 import java.nio.ByteBuffer;
23 import java.util.LinkedList;
24 import java.util.Queue;
25 import java.util.concurrent.ConcurrentLinkedQueue;
26
27 import javax.net.ssl.SSLContext;
28 import javax.net.ssl.SSLEngine;
29 import javax.net.ssl.SSLEngineResult;
30 import javax.net.ssl.SSLException;
31 import javax.net.ssl.SSLHandshakeException;
32 import javax.net.ssl.SSLSession;
33
34 import org.apache.mina.common.IoSession;
35 import org.apache.mina.common.WriteFuture;
36 import org.apache.mina.common.IoFilter.NextFilter;
37 import org.apache.mina.common.IoFilter.WriteRequest;
38 import org.apache.mina.common.support.DefaultWriteFuture;
39 import org.apache.mina.filter.SSLFilter;
40 import org.apache.mina.util.SessionLog;
41
42
43
44
45
46
47
48
49
50
51
52
53 public class SSLHandler {
54 private final SSLFilter parent;
55
56 private final SSLContext ctx;
57
58 private final IoSession session;
59
60 private final Queue<Event> preHandshakeEventQueue = new LinkedList<Event>();
61
62 private final Queue<Event> filterWriteEventQueue = new ConcurrentLinkedQueue<Event>();
63
64 private final Queue<Event> messageReceivedEventQueue = new ConcurrentLinkedQueue<Event>();
65
66 private SSLEngine sslEngine;
67
68
69
70
71 private ByteBuffer inNetBuffer;
72
73
74
75
76 private ByteBuffer outNetBuffer;
77
78
79
80
81 private ByteBuffer appBuffer;
82
83
84
85
86 private final ByteBuffer hsBB = ByteBuffer.allocate(0);
87
88
89
90
91 private SSLEngineResult.HandshakeStatus initialHandshakeStatus;
92
93
94
95
96 private boolean initialHandshakeComplete;
97
98 private boolean writingEncryptedData;
99
100
101
102
103
104
105
106 public SSLHandler(SSLFilter parent, SSLContext sslc, IoSession session)
107 throws SSLException {
108 this.parent = parent;
109 this.session = session;
110 this.ctx = sslc;
111 init();
112 }
113
114 public void init() throws SSLException {
115 if (sslEngine != null) {
116 return;
117 }
118
119 sslEngine = ctx.createSSLEngine();
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.getEnabledCipherSuites());
132 }
133
134 if (parent.getEnabledProtocols() != null) {
135 sslEngine.setEnabledProtocols(parent.getEnabledProtocols());
136 }
137
138 sslEngine.beginHandshake();
139 initialHandshakeStatus = sslEngine.getHandshakeStatus();
140 initialHandshakeComplete = false;
141
142 SSLByteBufferPool.initiate(sslEngine);
143
144 appBuffer = SSLByteBufferPool.getApplicationBuffer();
145
146 inNetBuffer = SSLByteBufferPool.getPacketBuffer();
147 outNetBuffer = SSLByteBufferPool.getPacketBuffer();
148 outNetBuffer.position(0);
149 outNetBuffer.limit(0);
150
151 writingEncryptedData = false;
152 }
153
154
155
156
157 public void destroy() {
158 if (sslEngine == null) {
159 return;
160 }
161
162
163 try {
164 sslEngine.closeInbound();
165 } catch (SSLException e) {
166 SessionLog.debug(session,
167 "Unexpected exception from SSLEngine.closeInbound().", e);
168 }
169
170 try {
171 do {
172 outNetBuffer.clear();
173 } while (sslEngine.wrap(hsBB, outNetBuffer).bytesProduced() > 0);
174 } catch (SSLException e) {
175 SessionLog.debug(session,
176 "Unexpected exception from SSLEngine.wrap().", e);
177 }
178 sslEngine.closeOutbound();
179 sslEngine = null;
180
181 SSLByteBufferPool.release(appBuffer);
182 SSLByteBufferPool.release(inNetBuffer);
183 SSLByteBufferPool.release(outNetBuffer);
184 preHandshakeEventQueue.clear();
185
186 }
187
188 public SSLFilter getParent() {
189 return parent;
190 }
191
192 public IoSession getSession() {
193 return session;
194 }
195
196
197
198
199 public boolean isWritingEncryptedData() {
200 return writingEncryptedData;
201 }
202
203
204
205
206 public boolean isInitialHandshakeComplete() {
207 return initialHandshakeComplete;
208 }
209
210 public boolean isInboundDone() {
211 return sslEngine == null || sslEngine.isInboundDone();
212 }
213
214 public boolean isOutboundDone() {
215 return sslEngine == null || sslEngine.isOutboundDone();
216 }
217
218
219
220
221 public boolean needToCompleteInitialHandshake() {
222 return (initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP && !isInboundDone());
223 }
224
225 public void schedulePreHandshakeWriteRequest(NextFilter nextFilter,
226 WriteRequest writeRequest) {
227 preHandshakeEventQueue.offer(new Event(EventType.FILTER_WRITE,
228 nextFilter, writeRequest));
229 }
230
231 public void flushPreHandshakeEvents() throws SSLException {
232 Event scheduledWrite;
233
234 while ((scheduledWrite = preHandshakeEventQueue.poll()) != null) {
235 if (SessionLog.isDebugEnabled(session)) {
236 SessionLog.debug(session, " Flushing buffered write request: "
237 + scheduledWrite.data);
238 }
239 parent.filterWrite(scheduledWrite.nextFilter, session,
240 (WriteRequest) scheduledWrite.data);
241 }
242 }
243
244 public void scheduleFilterWrite(NextFilter nextFilter,
245 WriteRequest writeRequest) {
246 filterWriteEventQueue.offer(new Event(EventType.FILTER_WRITE,
247 nextFilter, writeRequest));
248 }
249
250 public void scheduleMessageReceived(NextFilter nextFilter,
251 Object message) {
252 messageReceivedEventQueue.offer(new Event(EventType.RECEIVED, nextFilter,
253 message));
254 }
255
256 public void flushScheduledEvents() {
257
258 if (Thread.holdsLock(this)) {
259 return;
260 }
261
262 Event e;
263
264
265
266 synchronized (this) {
267 while ((e = filterWriteEventQueue.poll()) != null) {
268 e.nextFilter.filterWrite(session, (WriteRequest) e.data);
269 }
270 }
271
272 while ((e = messageReceivedEventQueue.poll()) != null) {
273 e.nextFilter.messageReceived(session, e.data);
274 }
275 }
276
277
278
279
280
281
282
283
284
285 public void messageReceived(NextFilter nextFilter, ByteBuffer buf)
286 throws SSLException {
287 if (buf.limit() > inNetBuffer.remaining()) {
288
289 inNetBuffer = SSLByteBufferPool.expandBuffer(inNetBuffer,
290 inNetBuffer.capacity() + (buf.limit() * 2));
291
292 appBuffer = SSLByteBufferPool.expandBuffer(appBuffer, inNetBuffer
293 .capacity() * 2);
294 appBuffer.position(0);
295 appBuffer.limit(0);
296 if (SessionLog.isDebugEnabled(session)) {
297 SessionLog.debug(session, " expanded inNetBuffer:"
298 + inNetBuffer);
299 SessionLog.debug(session, " expanded appBuffer:" + appBuffer);
300 }
301 }
302
303
304 inNetBuffer.put(buf);
305 if (!initialHandshakeComplete) {
306 handshake(nextFilter);
307 } else {
308 decrypt();
309 }
310
311 if (isInboundDone()) {
312
313 buf.position(buf.position() - inNetBuffer.position());
314 inNetBuffer.clear();
315 }
316 }
317
318
319
320
321
322
323 public ByteBuffer getAppBuffer() {
324 return appBuffer;
325 }
326
327
328
329
330
331
332 public ByteBuffer getOutNetBuffer() {
333 return outNetBuffer;
334 }
335
336
337
338
339
340
341
342 public void encrypt(ByteBuffer src) throws SSLException {
343 if (!initialHandshakeComplete) {
344 throw new IllegalStateException();
345 }
346
347
348
349 outNetBuffer.clear();
350
351
352 while (src.hasRemaining()) {
353
354 if (src.remaining() > ((outNetBuffer.capacity() - outNetBuffer
355 .position()) / 2)) {
356
357
358
359 outNetBuffer = SSLByteBufferPool.expandBuffer(outNetBuffer, src
360 .capacity() * 2);
361 if (SessionLog.isDebugEnabled(session)) {
362 SessionLog.debug(session, " expanded outNetBuffer:"
363 + outNetBuffer);
364 }
365 }
366
367 SSLEngineResult result = sslEngine.wrap(src, outNetBuffer);
368 if (SessionLog.isDebugEnabled(session)) {
369 SessionLog.debug(session, " Wrap res:" + result);
370 }
371
372 if (result.getStatus() == SSLEngineResult.Status.OK) {
373 if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
374 doTasks();
375 }
376 } else {
377 throw new SSLException("SSLEngine error during encrypt: "
378 + result.getStatus() + " src: " + src
379 + "outNetBuffer: " + outNetBuffer);
380 }
381 }
382
383 outNetBuffer.flip();
384 }
385
386
387
388
389
390
391
392
393
394 public boolean closeOutbound() throws SSLException {
395 if (sslEngine == null || sslEngine.isOutboundDone()) {
396 return false;
397 }
398
399 sslEngine.closeOutbound();
400
401
402
403 outNetBuffer.clear();
404 SSLEngineResult result = sslEngine.wrap(hsBB, outNetBuffer);
405 if (result.getStatus() != SSLEngineResult.Status.CLOSED) {
406 throw new SSLException("Improper close state: " + result);
407 }
408 outNetBuffer.flip();
409 return true;
410 }
411
412
413
414
415
416
417 private void decrypt() throws SSLException {
418
419 if (!initialHandshakeComplete) {
420 throw new IllegalStateException();
421 }
422
423 if (appBuffer.hasRemaining()) {
424 if (SessionLog.isDebugEnabled(session)) {
425 SessionLog.debug(session, " Error: appBuffer not empty!");
426 }
427
428 throw new IllegalStateException();
429 }
430
431 unwrap();
432 }
433
434
435
436
437
438 private SSLEngineResult.Status checkStatus(SSLEngineResult.Status status)
439 throws SSLException {
440 if (status != SSLEngineResult.Status.OK
441 && status != SSLEngineResult.Status.CLOSED
442 && status != SSLEngineResult.Status.BUFFER_UNDERFLOW) {
443 throw new SSLException("SSLEngine error during decrypt: " + status
444 + " inNetBuffer: " + inNetBuffer + "appBuffer: "
445 + appBuffer);
446 }
447
448 return status;
449 }
450
451
452
453
454 public void handshake(NextFilter nextFilter) throws SSLException {
455 if (SessionLog.isDebugEnabled(session)) {
456 SessionLog.debug(session, " doHandshake()");
457 }
458
459 while (!initialHandshakeComplete) {
460 if (initialHandshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED) {
461 session.setAttribute(SSLFilter.SSL_SESSION, sslEngine
462 .getSession());
463 if (SessionLog.isDebugEnabled(session)) {
464 SSLSession sslSession = sslEngine.getSession();
465 SessionLog.debug(session,
466 " initialHandshakeStatus=FINISHED");
467 SessionLog.debug(session, " sslSession CipherSuite used "
468 + sslSession.getCipherSuite());
469 }
470 initialHandshakeComplete = true;
471 if (session.containsAttribute(SSLFilter.USE_NOTIFICATION)) {
472 scheduleMessageReceived(nextFilter,
473 SSLFilter.SESSION_SECURED);
474 }
475 break;
476 } else if (initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) {
477 if (SessionLog.isDebugEnabled(session)) {
478 SessionLog.debug(session,
479 " initialHandshakeStatus=NEED_TASK");
480 }
481 initialHandshakeStatus = doTasks();
482 } else if (initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
483
484 if (SessionLog.isDebugEnabled(session)) {
485 SessionLog.debug(session,
486 " initialHandshakeStatus=NEED_UNWRAP");
487 }
488 SSLEngineResult.Status status = unwrapHandshake();
489 if ((initialHandshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED && status == SSLEngineResult.Status.BUFFER_UNDERFLOW)
490 || isInboundDone()) {
491
492 break;
493 }
494 } else if (initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
495 if (SessionLog.isDebugEnabled(session)) {
496 SessionLog.debug(session,
497 " initialHandshakeStatus=NEED_WRAP");
498 }
499
500
501 if (outNetBuffer.hasRemaining()) {
502 if (SessionLog.isDebugEnabled(session)) {
503 SessionLog
504 .debug(session, " Still data in out buffer!");
505 }
506 break;
507 }
508 outNetBuffer.clear();
509 SSLEngineResult result = sslEngine.wrap(hsBB, outNetBuffer);
510 if (SessionLog.isDebugEnabled(session)) {
511 SessionLog.debug(session, " Wrap res:" + result);
512 }
513
514 outNetBuffer.flip();
515 initialHandshakeStatus = result.getHandshakeStatus();
516 writeNetBuffer(nextFilter);
517 } else {
518 throw new IllegalStateException("Invalid Handshaking State"
519 + initialHandshakeStatus);
520 }
521 }
522 }
523
524 public WriteFuture writeNetBuffer(NextFilter nextFilter)
525 throws SSLException {
526
527 if (!getOutNetBuffer().hasRemaining()) {
528
529 return DefaultWriteFuture.newNotWrittenFuture(session);
530 }
531
532
533
534 writingEncryptedData = true;
535
536
537 WriteFuture writeFuture = null;
538
539 try {
540 if (SessionLog.isDebugEnabled(session)) {
541 SessionLog.debug(session, " write outNetBuffer: "
542 + getOutNetBuffer());
543 }
544 org.apache.mina.common.ByteBuffer writeBuffer = copy(getOutNetBuffer());
545 if (SessionLog.isDebugEnabled(session)) {
546 SessionLog.debug(session, " session write: " + writeBuffer);
547 }
548
549
550 writeFuture = new DefaultWriteFuture(session);
551 parent.filterWrite(nextFilter, session, new WriteRequest(
552 writeBuffer, writeFuture));
553
554
555 while (needToCompleteInitialHandshake()) {
556 try {
557 handshake(nextFilter);
558 } catch (SSLException ssle) {
559 SSLException newSSLE = new SSLHandshakeException(
560 "Initial SSL handshake failed.");
561 newSSLE.initCause(ssle);
562 throw newSSLE;
563 }
564 if (getOutNetBuffer().hasRemaining()) {
565 if (SessionLog.isDebugEnabled(session)) {
566 SessionLog.debug(session, " write outNetBuffer2: "
567 + getOutNetBuffer());
568 }
569 org.apache.mina.common.ByteBuffer writeBuffer2 = copy(getOutNetBuffer());
570 writeFuture = new DefaultWriteFuture(session);
571 parent.filterWrite(nextFilter, session, new WriteRequest(
572 writeBuffer2, writeFuture));
573 }
574 }
575 } finally {
576 writingEncryptedData = false;
577 }
578
579 return writeFuture;
580 }
581
582 private SSLEngineResult.Status unwrap() throws SSLException {
583 if (SessionLog.isDebugEnabled(session)) {
584 SessionLog.debug(session, " unwrap()");
585 }
586
587 appBuffer.clear();
588
589
590 inNetBuffer.flip();
591
592 SSLEngineResult res;
593 do {
594 if (SessionLog.isDebugEnabled(session)) {
595 SessionLog.debug(session, " inNetBuffer: " + inNetBuffer);
596 SessionLog.debug(session, " appBuffer: " + appBuffer);
597 }
598 res = sslEngine.unwrap(inNetBuffer, appBuffer);
599 if (SessionLog.isDebugEnabled(session)) {
600 SessionLog.debug(session, " Unwrap res:" + res);
601 }
602 } while (res.getStatus() == SSLEngineResult.Status.OK);
603
604
605 inNetBuffer.compact();
606
607 appBuffer.flip();
608
609
610
611
612
613
614
615
616
617 return checkStatus(res.getStatus());
618 }
619
620 private SSLEngineResult.Status unwrapHandshake() throws SSLException {
621 if (SessionLog.isDebugEnabled(session)) {
622 SessionLog.debug(session, " unwrapHandshake()");
623 }
624
625 appBuffer.clear();
626
627
628 inNetBuffer.flip();
629
630 SSLEngineResult res;
631 do {
632 if (SessionLog.isDebugEnabled(session)) {
633 SessionLog.debug(session, " inNetBuffer: " + inNetBuffer);
634 SessionLog.debug(session, " appBuffer: " + appBuffer);
635 }
636 res = sslEngine.unwrap(inNetBuffer, appBuffer);
637 if (SessionLog.isDebugEnabled(session)) {
638 SessionLog.debug(session, " Unwrap res:" + res);
639 }
640
641 } while (res.getStatus() == SSLEngineResult.Status.OK
642 && res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP);
643
644 initialHandshakeStatus = res.getHandshakeStatus();
645
646
647
648 if (initialHandshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED
649 && appBuffer.position() == 0
650 && res.getStatus() == SSLEngineResult.Status.OK
651 && inNetBuffer.hasRemaining()) {
652 do {
653 if (SessionLog.isDebugEnabled(session)) {
654 SessionLog.debug(session, " extra handshake unwrap");
655 SessionLog.debug(session, " inNetBuffer: " + inNetBuffer);
656 SessionLog.debug(session, " appBuffer: " + appBuffer);
657 }
658 res = sslEngine.unwrap(inNetBuffer, appBuffer);
659 if (SessionLog.isDebugEnabled(session)) {
660 SessionLog.debug(session, " Unwrap res:" + res);
661 }
662 } while (res.getStatus() == SSLEngineResult.Status.OK);
663 }
664
665
666 inNetBuffer.compact();
667
668
669 appBuffer.flip();
670
671
672
673
674
675
676
677
678
679
680 return checkStatus(res.getStatus());
681 }
682
683
684
685
686 private SSLEngineResult.HandshakeStatus doTasks() {
687 if (SessionLog.isDebugEnabled(session)) {
688 SessionLog.debug(session, " doTasks()");
689 }
690
691
692
693
694
695 Runnable runnable;
696 while ((runnable = sslEngine.getDelegatedTask()) != null) {
697 if (SessionLog.isDebugEnabled(session)) {
698 SessionLog.debug(session, " doTask: " + runnable);
699 }
700 runnable.run();
701 }
702 if (SessionLog.isDebugEnabled(session)) {
703 SessionLog.debug(session, " doTasks(): "
704 + sslEngine.getHandshakeStatus());
705 }
706 return sslEngine.getHandshakeStatus();
707 }
708
709
710
711
712
713
714
715
716 public static org.apache.mina.common.ByteBuffer copy(java.nio.ByteBuffer src) {
717 org.apache.mina.common.ByteBuffer copy = org.apache.mina.common.ByteBuffer
718 .allocate(src.remaining());
719 copy.put(src);
720 copy.flip();
721 return copy;
722 }
723
724 private static class EventType {
725 public static final EventType RECEIVED = new EventType("RECEIVED");
726
727 public static final EventType FILTER_WRITE = new EventType(
728 "FILTER_WRITE");
729
730 private final String value;
731
732 private EventType(String value) {
733 this.value = value;
734 }
735
736 public String toString() {
737 return value;
738 }
739 }
740
741 private static class Event {
742 private final EventType type;
743
744 private final NextFilter nextFilter;
745
746 private final Object data;
747
748 Event(EventType type, NextFilter nextFilter, Object data) {
749 this.type = type;
750 this.nextFilter = nextFilter;
751 this.data = data;
752 }
753
754 public Object getData() {
755 return data;
756 }
757
758 public NextFilter getNextFilter() {
759 return nextFilter;
760 }
761
762 public EventType getType() {
763 return type;
764 }
765 }
766 }