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 handshakeStatus;
93
94 private boolean initialHandshakeComplete;
95
96
97
98
99 private boolean handshakeComplete;
100
101 private boolean writingEncryptedData;
102
103
104
105
106
107
108
109 public SSLHandler(SSLFilter parent, SSLContext sslc, IoSession session)
110 throws SSLException {
111 this.parent = parent;
112 this.session = session;
113 this.ctx = sslc;
114 init();
115 }
116
117 public void init() throws SSLException {
118 if (sslEngine != null) {
119 return;
120 }
121
122 sslEngine = ctx.createSSLEngine();
123 sslEngine.setUseClientMode(parent.isUseClientMode());
124
125 if (parent.isWantClientAuth()) {
126 sslEngine.setWantClientAuth(true);
127 }
128
129 if (parent.isNeedClientAuth()) {
130 sslEngine.setNeedClientAuth(true);
131 }
132
133 if (parent.getEnabledCipherSuites() != null) {
134 sslEngine.setEnabledCipherSuites(parent.getEnabledCipherSuites());
135 }
136
137 if (parent.getEnabledProtocols() != null) {
138 sslEngine.setEnabledProtocols(parent.getEnabledProtocols());
139 }
140
141 sslEngine.beginHandshake();
142 handshakeStatus = sslEngine.getHandshakeStatus();
143 handshakeComplete = false;
144 initialHandshakeComplete = false;
145
146 SSLByteBufferPool.initiate(sslEngine);
147
148 appBuffer = SSLByteBufferPool.getApplicationBuffer();
149
150 inNetBuffer = SSLByteBufferPool.getPacketBuffer();
151 outNetBuffer = SSLByteBufferPool.getPacketBuffer();
152 outNetBuffer.position(0);
153 outNetBuffer.limit(0);
154
155 writingEncryptedData = false;
156 }
157
158
159
160
161 public void destroy() {
162 if (sslEngine == null) {
163 return;
164 }
165
166
167 try {
168 sslEngine.closeInbound();
169 } catch (SSLException e) {
170 SessionLog.debug(session,
171 "Unexpected exception from SSLEngine.closeInbound().", e);
172 }
173
174 try {
175 do {
176 outNetBuffer.clear();
177 } while (sslEngine.wrap(hsBB, outNetBuffer).bytesProduced() > 0);
178 } catch (SSLException e) {
179 SessionLog.debug(session,
180 "Unexpected exception from SSLEngine.wrap().", e);
181 }
182 sslEngine.closeOutbound();
183 sslEngine = null;
184
185 SSLByteBufferPool.release(appBuffer);
186 SSLByteBufferPool.release(inNetBuffer);
187 SSLByteBufferPool.release(outNetBuffer);
188 preHandshakeEventQueue.clear();
189 }
190
191 public SSLFilter getParent() {
192 return parent;
193 }
194
195 public IoSession getSession() {
196 return session;
197 }
198
199
200
201
202 public boolean isWritingEncryptedData() {
203 return writingEncryptedData;
204 }
205
206
207
208
209 public boolean isHandshakeComplete() {
210 return handshakeComplete;
211 }
212
213 public boolean isInboundDone() {
214 return sslEngine == null || sslEngine.isInboundDone();
215 }
216
217 public boolean isOutboundDone() {
218 return sslEngine == null || sslEngine.isOutboundDone();
219 }
220
221
222
223
224 public boolean needToCompleteHandshake() {
225 return (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP && !isInboundDone());
226 }
227
228 public void schedulePreHandshakeWriteRequest(NextFilter nextFilter,
229 WriteRequest writeRequest) {
230 preHandshakeEventQueue.offer(new Event(EventType.FILTER_WRITE,
231 nextFilter, writeRequest));
232 }
233
234 public void flushPreHandshakeEvents() throws SSLException {
235 Event scheduledWrite;
236
237 while ((scheduledWrite = (Event) preHandshakeEventQueue.poll()) != null) {
238 if (SessionLog.isDebugEnabled(session)) {
239 SessionLog.debug(session, " Flushing buffered write request: "
240 + scheduledWrite.data);
241 }
242 parent.filterWrite(scheduledWrite.nextFilter, session,
243 (WriteRequest) scheduledWrite.data);
244 }
245 }
246
247 public void scheduleFilterWrite(NextFilter nextFilter,
248 WriteRequest writeRequest) {
249 filterWriteEventQueue.offer(new Event(EventType.FILTER_WRITE,
250 nextFilter, writeRequest));
251 }
252
253 public void scheduleMessageReceived(NextFilter nextFilter,
254 Object message) {
255 messageReceivedEventQueue.offer(new Event(EventType.RECEIVED, nextFilter,
256 message));
257 }
258
259 public void flushScheduledEvents() {
260
261 if (Thread.holdsLock(this)) {
262 return;
263 }
264
265 Event e;
266
267
268
269 synchronized (this) {
270 while ((e = (Event) filterWriteEventQueue.poll()) != null) {
271 e.nextFilter.filterWrite(session, (WriteRequest) e.data);
272 }
273 }
274
275 while ((e = (Event) messageReceivedEventQueue.poll()) != null) {
276 e.nextFilter.messageReceived(session, e.data);
277 }
278 }
279
280
281
282
283
284
285
286
287
288 public void messageReceived(NextFilter nextFilter, ByteBuffer buf)
289 throws SSLException {
290 if (buf.limit() > inNetBuffer.remaining()) {
291
292 inNetBuffer = SSLByteBufferPool.expandBuffer(inNetBuffer,
293 inNetBuffer.capacity() + (buf.limit() * 2));
294
295 appBuffer = SSLByteBufferPool.expandBuffer(appBuffer, inNetBuffer
296 .capacity() * 2);
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 (!handshakeComplete) {
307 handshake(nextFilter);
308 } else {
309 decrypt(nextFilter);
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 (!handshakeComplete) {
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(NextFilter nextFilter) throws SSLException {
421
422 if (!handshakeComplete) {
423 throw new IllegalStateException();
424 }
425
426 unwrap(nextFilter);
427 }
428
429
430
431
432
433 private void checkStatus(SSLEngineResult res)
434 throws SSLException {
435
436 SSLEngineResult.Status status = res.getStatus();
437
438
439
440
441
442
443
444
445
446 if (status != SSLEngineResult.Status.OK
447 && status != SSLEngineResult.Status.CLOSED
448 && status != SSLEngineResult.Status.BUFFER_UNDERFLOW) {
449 throw new SSLException("SSLEngine error during decrypt: " + status
450 + " inNetBuffer: " + inNetBuffer + "appBuffer: "
451 + appBuffer);
452 }
453 }
454
455
456
457
458 public void handshake(NextFilter nextFilter) throws SSLException {
459 if (SessionLog.isDebugEnabled(session)) {
460 SessionLog.debug(session, " doHandshake()");
461 }
462
463 for (;;) {
464 if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED) {
465 session.setAttribute(SSLFilter.SSL_SESSION, sslEngine
466 .getSession());
467 if (SessionLog.isDebugEnabled(session)) {
468 SSLSession sslSession = sslEngine.getSession();
469 SessionLog.debug(session,
470 " handshakeStatus=FINISHED");
471 SessionLog.debug(session, " sslSession CipherSuite used "
472 + sslSession.getCipherSuite());
473 }
474 handshakeComplete = true;
475 if (!initialHandshakeComplete
476 && session.containsAttribute(SSLFilter.USE_NOTIFICATION)) {
477
478
479 initialHandshakeComplete = true;
480 scheduleMessageReceived(nextFilter,
481 SSLFilter.SESSION_SECURED);
482 }
483 break;
484 } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) {
485 if (SessionLog.isDebugEnabled(session)) {
486 SessionLog.debug(session,
487 " handshakeStatus=NEED_TASK");
488 }
489 handshakeStatus = doTasks();
490 } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
491
492 if (SessionLog.isDebugEnabled(session)) {
493 SessionLog.debug(session,
494 " handshakeStatus=NEED_UNWRAP");
495 }
496 SSLEngineResult.Status status = unwrapHandshake(nextFilter);
497 if (status == SSLEngineResult.Status.BUFFER_UNDERFLOW
498 || isInboundDone()) {
499
500 break;
501 }
502 } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
503 if (SessionLog.isDebugEnabled(session)) {
504 SessionLog.debug(session,
505 " handshakeStatus=NEED_WRAP");
506 }
507
508
509 if (outNetBuffer.hasRemaining()) {
510 if (SessionLog.isDebugEnabled(session)) {
511 SessionLog
512 .debug(session, " Still data in out buffer!");
513 }
514 break;
515 }
516 outNetBuffer.clear();
517 SSLEngineResult result = sslEngine.wrap(hsBB, outNetBuffer);
518 if (SessionLog.isDebugEnabled(session)) {
519 SessionLog.debug(session, " Wrap res:" + result);
520 }
521
522 outNetBuffer.flip();
523 handshakeStatus = result.getHandshakeStatus();
524 writeNetBuffer(nextFilter);
525 } else {
526 throw new IllegalStateException("Invalid Handshaking State"
527 + handshakeStatus);
528 }
529 }
530 }
531
532 public WriteFuture writeNetBuffer(NextFilter nextFilter)
533 throws SSLException {
534
535 if (!getOutNetBuffer().hasRemaining()) {
536
537 return DefaultWriteFuture.newNotWrittenFuture(session);
538 }
539
540 WriteFuture writeFuture = null;
541
542
543
544
545
546 writingEncryptedData = true;
547
548 try {
549 if (SessionLog.isDebugEnabled(session)) {
550 SessionLog.debug(session, " write outNetBuffer: "
551 + getOutNetBuffer());
552 }
553 org.apache.mina.common.ByteBuffer writeBuffer = copy(getOutNetBuffer());
554 if (SessionLog.isDebugEnabled(session)) {
555 SessionLog.debug(session, " session write: " + writeBuffer);
556 }
557
558
559 writeFuture = new DefaultWriteFuture(session);
560 parent.filterWrite(nextFilter, session, new WriteRequest(
561 writeBuffer, writeFuture));
562
563
564 while (needToCompleteHandshake()) {
565 try {
566 handshake(nextFilter);
567 } catch (SSLException ssle) {
568 SSLException newSSLE = new SSLHandshakeException(
569 "SSL handshake failed.");
570 newSSLE.initCause(ssle);
571 throw newSSLE;
572 }
573 if (getOutNetBuffer().hasRemaining()) {
574 if (SessionLog.isDebugEnabled(session)) {
575 SessionLog.debug(session, " write outNetBuffer2: "
576 + getOutNetBuffer());
577 }
578 org.apache.mina.common.ByteBuffer writeBuffer2 = copy(getOutNetBuffer());
579 writeFuture = new DefaultWriteFuture(session);
580 parent.filterWrite(nextFilter, session, new WriteRequest(
581 writeBuffer2, writeFuture));
582 }
583 }
584 } finally {
585 writingEncryptedData = false;
586 }
587
588 if (writeFuture != null) {
589 return writeFuture;
590 } else {
591 return DefaultWriteFuture.newNotWrittenFuture(session);
592 }
593 }
594
595 private void unwrap(NextFilter nextFilter) throws SSLException {
596 if (SessionLog.isDebugEnabled(session)) {
597 SessionLog.debug(session, " unwrap()");
598 }
599
600
601 inNetBuffer.flip();
602
603 SSLEngineResult res = unwrap0();
604
605
606 inNetBuffer.compact();
607
608 checkStatus(res);
609
610 renegotiateIfNeeded(nextFilter, res);
611 }
612
613 private SSLEngineResult.Status unwrapHandshake(NextFilter nextFilter) throws SSLException {
614 if (SessionLog.isDebugEnabled(session)) {
615 SessionLog.debug(session, " unwrapHandshake()");
616 }
617
618
619 inNetBuffer.flip();
620
621 SSLEngineResult res = unwrap0();
622 handshakeStatus = res.getHandshakeStatus();
623
624 checkStatus(res);
625
626
627
628 if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED
629 && res.getStatus() == SSLEngineResult.Status.OK
630 && inNetBuffer.hasRemaining()) {
631 res = unwrap0();
632
633
634 inNetBuffer.compact();
635
636 renegotiateIfNeeded(nextFilter, res);
637 } else {
638
639 inNetBuffer.compact();
640 }
641
642 return res.getStatus();
643 }
644
645 private void renegotiateIfNeeded(NextFilter nextFilter, SSLEngineResult res)
646 throws SSLException {
647 if (res.getStatus() != SSLEngineResult.Status.CLOSED
648 && res.getStatus() != SSLEngineResult.Status.BUFFER_UNDERFLOW
649 && res.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
650
651 SessionLog.debug(session, " Renegotiating...");
652 handshakeComplete = false;
653 handshakeStatus = res.getHandshakeStatus();
654 handshake(nextFilter);
655 }
656 }
657
658 private SSLEngineResult unwrap0() throws SSLException {
659 SSLEngineResult res;
660 do {
661 if (SessionLog.isDebugEnabled(session)) {
662 SessionLog.debug(session, " inNetBuffer: " + inNetBuffer);
663 SessionLog.debug(session, " appBuffer: " + appBuffer);
664 }
665 res = sslEngine.unwrap(inNetBuffer, appBuffer);
666 if (SessionLog.isDebugEnabled(session)) {
667 SessionLog.debug(session, " Unwrap res:" + res);
668 }
669 } while (res.getStatus() == SSLEngineResult.Status.OK
670 && (handshakeComplete && res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING
671 || res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP));
672
673 return res;
674 }
675
676
677
678
679 private SSLEngineResult.HandshakeStatus doTasks() {
680 if (SessionLog.isDebugEnabled(session)) {
681 SessionLog.debug(session, " doTasks()");
682 }
683
684
685
686
687
688 Runnable runnable;
689 while ((runnable = sslEngine.getDelegatedTask()) != null) {
690 if (SessionLog.isDebugEnabled(session)) {
691 SessionLog.debug(session, " doTask: " + runnable);
692 }
693 runnable.run();
694 }
695 if (SessionLog.isDebugEnabled(session)) {
696 SessionLog.debug(session, " doTasks(): "
697 + sslEngine.getHandshakeStatus());
698 }
699 return sslEngine.getHandshakeStatus();
700 }
701
702
703
704
705
706
707
708
709 public static org.apache.mina.common.ByteBuffer copy(java.nio.ByteBuffer src) {
710 org.apache.mina.common.ByteBuffer copy = org.apache.mina.common.ByteBuffer
711 .allocate(src.remaining());
712 copy.put(src);
713 copy.flip();
714 return copy;
715 }
716
717 private static class EventType {
718 public static final EventType RECEIVED = new EventType("RECEIVED");
719
720 public static final EventType FILTER_WRITE = new EventType(
721 "FILTER_WRITE");
722
723 private final String value;
724
725 private EventType(String value) {
726 this.value = value;
727 }
728
729 public String toString() {
730 return value;
731 }
732 }
733
734 private static class Event {
735 private final EventType type;
736
737 private final NextFilter nextFilter;
738
739 private final Object data;
740
741 Event(EventType type, NextFilter nextFilter, Object data) {
742 this.type = type;
743 this.nextFilter = nextFilter;
744 this.data = data;
745 }
746
747 public Object getData() {
748 return data;
749 }
750
751 public NextFilter getNextFilter() {
752 return nextFilter;
753 }
754
755 public EventType getType() {
756 return type;
757 }
758 }
759 }