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