1 // ========================================================================
2 // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // All rights reserved. This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v1.0
6 // and Apache License v2.0 which accompanies this distribution.
7 // The Eclipse Public License is available at
8 // http://www.eclipse.org/legal/epl-v10.html
9 // The Apache License v2.0 is available at
10 // http://www.opensource.org/licenses/apache2.0.php
11 // You may elect to redistribute this code under either of these licenses.
12 // ========================================================================
13
14 package org.eclipse.jetty.http.ssl;
15
16 import java.io.IOException;
17 import java.nio.ByteBuffer;
18 import java.nio.channels.SelectionKey;
19 import java.nio.channels.SocketChannel;
20
21 import javax.net.ssl.SSLEngine;
22 import javax.net.ssl.SSLEngineResult;
23 import javax.net.ssl.SSLException;
24 import javax.net.ssl.SSLSession;
25 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
26
27 import org.eclipse.jetty.http.Parser;
28 import org.eclipse.jetty.io.Buffer;
29 import org.eclipse.jetty.io.Buffers;
30 import org.eclipse.jetty.io.EofException;
31 import org.eclipse.jetty.io.nio.NIOBuffer;
32 import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
33 import org.eclipse.jetty.io.nio.SelectorManager;
34 import org.eclipse.jetty.util.log.Log;
35 import org.eclipse.jetty.util.log.Logger;
36
37 /* ------------------------------------------------------------ */
38 /**
39 * SslSelectChannelEndPoint
40 * <p>
41 * A SelectChannelEndPoint that uses an {@link SSLEngine} to handle an
42 * SSL connection.
43 * <p>
44 * There is a named logger "org.eclipse.jetty.http.ssl"
45 * </p>
46 */
47 public class SslSelectChannelEndPoint extends SelectChannelEndPoint
48 {
49 static Logger __log = Log.getLogger("org.eclipse.jetty.http.ssl");
50
51 private static final ByteBuffer[] __NO_BUFFERS={};
52
53 private final Buffers _buffers;
54
55 private final SSLEngine _engine;
56 private final SSLSession _session;
57 private final ByteBuffer _inBuffer;
58 private final NIOBuffer _inNIOBuffer;
59 private final ByteBuffer _outBuffer;
60 private final NIOBuffer _outNIOBuffer;
61
62 private final NIOBuffer[] _reuseBuffer=new NIOBuffer[2];
63 private final ByteBuffer[] _gather=new ByteBuffer[2];
64
65 private boolean _closing=false;
66 private SSLEngineResult _result;
67
68 private final boolean _debug = __log.isDebugEnabled(); // snapshot debug status for optimizer
69
70
71 /* ------------------------------------------------------------ */
72 public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine)
73 throws IOException
74 {
75 super(channel,selectSet,key);
76 _buffers=buffers;
77
78 // ssl
79 _engine=engine;
80 _session=engine.getSession();
81
82 // TODO pool buffers and use only when needed.
83 _outNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize());
84 _outBuffer=_outNIOBuffer.getByteBuffer();
85 _inNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize());
86 _inBuffer=_inNIOBuffer.getByteBuffer();
87
88 if (_debug) __log.debug(_session+" channel="+channel);
89 }
90
91 // TODO get rid of these dumps
92 public void dump()
93 {
94 Log.info(""+_result);
95 // System.err.println(h.toString());
96 // System.err.println("--");
97 }
98
99 /* ------------------------------------------------------------ */
100 /* (non-Javadoc)
101 * @see org.eclipse.io.nio.SelectChannelEndPoint#idleExpired()
102 */
103 protected void idleExpired()
104 {
105 try
106 {
107 getSelectManager().dispatch(new Runnable()
108 {
109 public void run()
110 {
111 doIdleExpired();
112 }
113 });
114 }
115 catch(Exception e)
116 {
117 Log.ignore(e);
118 }
119 }
120
121 /* ------------------------------------------------------------ */
122 protected void doIdleExpired()
123 {
124 super.idleExpired();
125 }
126
127 /* ------------------------------------------------------------ */
128 public void close() throws IOException
129 {
130 // TODO - this really should not be done in a loop here - but with async callbacks.
131
132 _closing=true;
133 try
134 {
135 if (isBufferingOutput())
136 {
137 flush();
138 while (isOpen() && isBufferingOutput())
139 {
140 Thread.sleep(100); // TODO non blocking
141 flush();
142 }
143 }
144
145 _engine.closeOutbound();
146
147 loop: while (isOpen() && !(_engine.isInboundDone() && _engine.isOutboundDone()))
148 {
149 if (isBufferingOutput())
150 {
151 flush();
152 while (isOpen() && isBufferingOutput())
153 {
154 Thread.sleep(100); // TODO non blocking
155 flush();
156 }
157 }
158
159 if (_debug) __log.debug(_session+" closing "+_engine.getHandshakeStatus());
160 switch(_engine.getHandshakeStatus())
161 {
162 case FINISHED:
163 case NOT_HANDSHAKING:
164 break loop;
165
166 case NEED_UNWRAP:
167 Buffer buffer =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
168 try
169 {
170 ByteBuffer bbuffer = ((NIOBuffer)buffer).getByteBuffer();
171 if (!unwrap(bbuffer) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
172 {
173 break loop;
174 }
175 }
176 catch(SSLException e)
177 {
178 Log.ignore(e);
179 }
180 finally
181 {
182 _buffers.returnBuffer(buffer);
183 }
184 break;
185
186 case NEED_TASK:
187 {
188 Runnable task;
189 while ((task=_engine.getDelegatedTask())!=null)
190 {
191 task.run();
192 }
193 break;
194 }
195
196 case NEED_WRAP:
197 {
198 try
199 {
200 _outNIOBuffer.compact();
201 int put=_outNIOBuffer.putIndex();
202 _outBuffer.position(put);
203 _result=null;
204 _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
205 if (_debug) __log.debug(_session+" close wrap "+_result);
206 _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
207 }
208 finally
209 {
210 _outBuffer.position(0);
211 }
212
213 break;
214 }
215 }
216 }
217 }
218 catch(IOException e)
219 {
220 Log.ignore(e);
221 }
222 catch (InterruptedException e)
223 {
224 Log.ignore(e);
225 }
226 finally
227 {
228 super.close();
229
230 if (_inNIOBuffer!=null)
231 _buffers.returnBuffer(_inNIOBuffer);
232 if (_outNIOBuffer!=null)
233 _buffers.returnBuffer(_outNIOBuffer);
234 if (_reuseBuffer[0]!=null)
235 _buffers.returnBuffer(_reuseBuffer[0]);
236 if (_reuseBuffer[1]!=null)
237 _buffers.returnBuffer(_reuseBuffer[1]);
238 }
239 }
240
241 /* ------------------------------------------------------------ */
242 /** Fill the buffer with unencrypted bytes.
243 * Called by a {@link Parser} instance when more data is
244 * needed to continue parsing a request or a response.
245 */
246 @Override
247 public int fill(Buffer buffer) throws IOException
248 {
249 // This end point only works on NIO buffer type (director
250 // or indirect), so extract the NIO buffer that is wrapped
251 // by the passed jetty Buffer.
252 final ByteBuffer bbuf=extractInputBuffer(buffer);
253
254 // remember the original size of the unencrypted buffer
255 int size=buffer.length();
256
257 HandshakeStatus initialStatus = _engine.getHandshakeStatus();
258 //noinspection SynchronizationOnLocalVariableOrMethodParameter
259 synchronized (bbuf)
260 {
261 try
262 {
263 // Call the SSLEngine unwrap method to process data in
264 // the inBuffer. If there is no data in the inBuffer, then
265 // super.fill is called to read encrypted bytes.
266 unwrap(bbuf);
267
268 // Loop through the SSL engine state machine
269
270 int wraps=0;
271 loop: while (true)
272 {
273 // If we have encrypted data in output buffer
274 if (isBufferingOutput())
275 {
276 // we must flush it, as the other end might be
277 // waiting for that outgoing data before sending
278 // more incoming data
279 flush();
280
281 // If we were unable to flush all the data, then
282 // we should break the loop and wait for the call
283 // back to handle when the SelectSet detects that
284 // the channel is writable again.
285 if (isBufferingOutput())
286 break loop;
287 }
288
289 // handle the current hand share status
290 switch(_engine.getHandshakeStatus())
291 {
292 case FINISHED:
293 case NOT_HANDSHAKING:
294 // If we are closing, then unwrap must have CLOSED result,
295 // so return -1 to signal upwards
296 if (_closing)
297 return -1;
298
299 // otherwise we break loop with the data we have unwrapped.
300 break loop;
301
302 case NEED_UNWRAP:
303 // Need more data to be unwrapped so try another call to unwrap
304 if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
305 {
306 // If the unwrap call did not make any progress and we are still in
307 // NEED_UNWRAP, then we should break the loop and wait for more data to
308 // arrive.
309 break loop;
310 }
311 // progress was made so continue the loop.
312 break;
313
314 case NEED_TASK:
315 {
316 // A task needs to be run, so run it!
317
318 Runnable task;
319 while ((task=_engine.getDelegatedTask())!=null)
320 {
321 task.run();
322 }
323
324 // Detect SUN JVM Bug!!!
325 if(initialStatus==HandshakeStatus.NOT_HANDSHAKING &&
326 _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0)
327 {
328 // This should be NEED_WRAP
329 // The fix simply detects the signature of the bug and then close the connection (fail-fast) so that ff3 will delegate to using SSL instead of TLS.
330 // This is a jvm bug on java1.6 where the SSLEngine expects more data from the initial handshake when the client(ff3-tls) already had given it.
331 // See http://jira.codehaus.org/browse/JETTY-567 for more details
332 if (_debug) __log.warn(_session+" JETTY-567");
333 return -1;
334 }
335 break;
336 }
337
338 case NEED_WRAP:
339 {
340 // The SSL needs to send some handshake data to the other side,
341 // so let fill become a flush for a little bit.
342 wraps++;
343 synchronized(_outBuffer)
344 {
345 try
346 {
347 // call wrap with empty application buffers, so it can
348 // generate required handshake messages into _outNIOBuffer
349 _outNIOBuffer.compact();
350 int put=_outNIOBuffer.putIndex();
351 _outBuffer.position();
352 _result=null;
353 _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
354 if (_debug) __log.debug(_session+" fill wrap "+_result);
355 switch(_result.getStatus())
356 {
357 case BUFFER_OVERFLOW:
358 case BUFFER_UNDERFLOW:
359 Log.warn("wrap {}",_result);
360 case CLOSED:
361 _closing=true;
362 }
363
364 _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
365 }
366 finally
367 {
368 _outBuffer.position(0);
369 }
370 }
371
372 // flush the encrypted outNIOBuffer
373 flush();
374
375 break;
376 }
377 }
378 }
379 }
380 catch(SSLException e)
381 {
382 Log.warn(e.toString());
383 Log.debug(e);
384 throw e;
385 }
386 finally
387 {
388 // reset the Buffers
389 buffer.setPutIndex(bbuf.position());
390 bbuf.position(0);
391 }
392 }
393
394 // return the number of unencrypted bytes filled.
395 return buffer.length()-size;
396
397 }
398
399 /* ------------------------------------------------------------ */
400 @Override
401 public int flush(Buffer buffer) throws IOException
402 {
403 return flush(buffer,null,null);
404 }
405
406
407 /* ------------------------------------------------------------ */
408 /*
409 */
410 @Override
411 public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
412 {
413 int consumed=0;
414 int available=header.length();
415 if (buffer!=null)
416 available+=buffer.length();
417
418 int tries=0;
419 loop: while (true)
420 {
421 if (_outNIOBuffer.length()>0)
422 {
423 flush();
424 if (isBufferingOutput())
425 break loop;
426 }
427
428 switch(_engine.getHandshakeStatus())
429 {
430 case FINISHED:
431 case NOT_HANDSHAKING:
432
433 if (_closing || available==0)
434 {
435 if (consumed==0)
436 consumed= -1;
437 break loop;
438 }
439
440 int c;
441 if (header!=null && header.length()>0)
442 {
443 if (buffer!=null && buffer.length()>0)
444 c=wrap(header,buffer);
445 else
446 c=wrap(header);
447 }
448 else
449 c=wrap(buffer);
450
451 if (c>0)
452 {
453 consumed+=c;
454 available-=c;
455 }
456 else if (c<0)
457 {
458 if (consumed==0)
459 consumed=-1;
460 break loop;
461 }
462
463 break;
464
465 case NEED_UNWRAP:
466 Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
467 try
468 {
469 ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer();
470 if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
471 {
472 break loop;
473 }
474 }
475 finally
476 {
477 _buffers.returnBuffer(buf);
478 }
479
480 break;
481
482 case NEED_TASK:
483 {
484 Runnable task;
485 while ((task=_engine.getDelegatedTask())!=null)
486 {
487 task.run();
488 }
489 break;
490 }
491
492 case NEED_WRAP:
493 {
494 synchronized(_outBuffer)
495 {
496 try
497 {
498 _outNIOBuffer.compact();
499 int put=_outNIOBuffer.putIndex();
500 _outBuffer.position();
501 _result=null;
502 _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
503 if (_debug) __log.debug(_session+" flush wrap "+_result);
504 switch(_result.getStatus())
505 {
506 case BUFFER_OVERFLOW:
507 case BUFFER_UNDERFLOW:
508 Log.warn("unwrap {}",_result);
509 case CLOSED:
510 _closing=true;
511 }
512 _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
513 }
514 finally
515 {
516 _outBuffer.position(0);
517 }
518 }
519
520 flush();
521 if (isBufferingOutput())
522 break loop;
523
524 break;
525 }
526 }
527 }
528
529 return consumed;
530 }
531
532 /* ------------------------------------------------------------ */
533 @Override
534 public void flush() throws IOException
535 {
536 int len=_outNIOBuffer.length();
537 if (isBufferingOutput())
538 {
539 int flushed=super.flush(_outNIOBuffer);
540 if (_debug) __log.debug(_session+" Flushed "+flushed+"/"+len);
541 if (isBufferingOutput())
542 {
543 // Try again after yield.... cheaper than a reschedule.
544 Thread.yield();
545 flushed=super.flush(_outNIOBuffer);
546 if (_debug) __log.debug(_session+" flushed "+flushed+"/"+len);
547 }
548 }
549 }
550
551 /* ------------------------------------------------------------ */
552 private ByteBuffer extractInputBuffer(Buffer buffer)
553 {
554 assert buffer instanceof NIOBuffer;
555 NIOBuffer nbuf=(NIOBuffer)buffer;
556 ByteBuffer bbuf=nbuf.getByteBuffer();
557 bbuf.position(buffer.putIndex());
558 return bbuf;
559 }
560
561 /* ------------------------------------------------------------ */
562 /**
563 * @return true if progress is made
564 */
565 private boolean unwrap(ByteBuffer buffer) throws IOException
566 {
567 if (_inNIOBuffer.hasContent())
568 _inNIOBuffer.compact();
569 else
570 _inNIOBuffer.clear();
571
572 int total_filled=0;
573
574 // loop filling as much encrypted data as we can into the buffer
575 while (_inNIOBuffer.space()>0 && super.isOpen())
576 {
577 try
578 {
579 int filled=super.fill(_inNIOBuffer);
580 if (_debug) __log.debug(_session+" unwrap filled "+filled);
581 // break the loop if no progress is made (we have read everything
582 // there is to read).
583 if (filled<=0)
584 break;
585 total_filled+=filled;
586 }
587 catch(IOException e)
588 {
589 if (_inNIOBuffer.length()==0)
590 {
591 _outNIOBuffer.clear();
592 throw e;
593 }
594 break;
595 }
596 }
597
598 // If we have no progress and no data
599 if (total_filled==0 && _inNIOBuffer.length()==0)
600 {
601 if(!isOpen())
602 {
603 _outNIOBuffer.clear();
604 throw new EofException();
605 }
606 return false;
607 }
608
609 // We have some in data, so try to unwrap it.
610 try
611 {
612 // inBuffer is the NIO buffer inside the _inNIOBuffer,
613 // so update its position and limit from the inNIOBuffer.
614 _inBuffer.position(_inNIOBuffer.getIndex());
615 _inBuffer.limit(_inNIOBuffer.putIndex());
616 _result=null;
617
618 // Do the unwrap
619 _result=_engine.unwrap(_inBuffer,buffer);
620 if (_debug) __log.debug(_session+" unwrap unwrap "+_result);
621
622 // skip the bytes consumed
623 _inNIOBuffer.skip(_result.bytesConsumed());
624 }
625 finally
626 {
627 // reset the buffer so it can be managed by the _inNIOBuffer again.
628 _inBuffer.position(0);
629 _inBuffer.limit(_inBuffer.capacity());
630 }
631
632 // handle the unwrap results
633 switch(_result.getStatus())
634 {
635 case BUFFER_OVERFLOW:
636 throw new IllegalStateException(_result.toString());
637
638 case BUFFER_UNDERFLOW:
639 // Not enough data,
640 // If we are closed, we will never get more, so EOF
641 // else return and we will be tried again
642 // later when more data arriving causes another dispatch.
643 if (Log.isDebugEnabled()) Log.debug("unwrap {}",_result);
644 if(!isOpen())
645 {
646 _inNIOBuffer.clear();
647 _outNIOBuffer.clear();
648 throw new EofException();
649 }
650 return (total_filled > 0);
651
652 case CLOSED:
653 _closing=true;
654 case OK:
655 // return true is some bytes somewhere were moved about.
656 return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0;
657 default:
658 Log.warn("unwrap "+_result);
659 throw new IOException(_result.toString());
660 }
661 }
662
663
664 /* ------------------------------------------------------------ */
665 private ByteBuffer extractOutputBuffer(Buffer buffer,int n)
666 {
667 if (buffer.buffer() instanceof NIOBuffer)
668 {
669 NIOBuffer nBuf=(NIOBuffer)buffer.buffer();
670 return nBuf.getByteBuffer();
671 }
672 else
673 {
674 if (_reuseBuffer[n]==null)
675 _reuseBuffer[n] = (NIOBuffer)_buffers.getBuffer(_session.getApplicationBufferSize());
676 NIOBuffer buf = _reuseBuffer[n];
677 buf.clear();
678 buf.put(buffer);
679 return buf.getByteBuffer();
680 }
681 }
682
683 /* ------------------------------------------------------------ */
684 private int wrap(final Buffer header, final Buffer buffer) throws IOException
685 {
686 _gather[0]=extractOutputBuffer(header,0);
687 synchronized(_gather[0])
688 {
689 _gather[0].position(header.getIndex());
690 _gather[0].limit(header.putIndex());
691
692 _gather[1]=extractOutputBuffer(buffer,1);
693
694 synchronized(_gather[1])
695 {
696 _gather[1].position(buffer.getIndex());
697 _gather[1].limit(buffer.putIndex());
698
699 synchronized(_outBuffer)
700 {
701 int consumed=0;
702 try
703 {
704 _outNIOBuffer.clear();
705 _outBuffer.position(0);
706 _outBuffer.limit(_outBuffer.capacity());
707
708 _result=null;
709 _result=_engine.wrap(_gather,_outBuffer);
710 if (_debug) __log.debug(_session+" wrap wrap "+_result);
711 _outNIOBuffer.setGetIndex(0);
712 _outNIOBuffer.setPutIndex(_result.bytesProduced());
713 consumed=_result.bytesConsumed();
714 }
715 finally
716 {
717 _outBuffer.position(0);
718
719 if (consumed>0)
720 {
721 int len=consumed<header.length()?consumed:header.length();
722 header.skip(len);
723 consumed-=len;
724 _gather[0].position(0);
725 _gather[0].limit(_gather[0].capacity());
726 }
727 if (consumed>0)
728 {
729 int len=consumed<buffer.length()?consumed:buffer.length();
730 buffer.skip(len);
731 consumed-=len;
732 _gather[1].position(0);
733 _gather[1].limit(_gather[1].capacity());
734 }
735 assert consumed==0;
736 }
737 }
738 }
739 }
740
741
742 switch(_result.getStatus())
743 {
744 case BUFFER_OVERFLOW:
745 case BUFFER_UNDERFLOW:
746 Log.warn("unwrap {}",_result);
747
748 case OK:
749 return _result.bytesConsumed();
750 case CLOSED:
751 _closing=true;
752 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
753
754 default:
755 Log.warn("wrap "+_result);
756 throw new IOException(_result.toString());
757 }
758 }
759
760 /* ------------------------------------------------------------ */
761 private int wrap(final Buffer buffer) throws IOException
762 {
763 _gather[0]=extractOutputBuffer(buffer,0);
764 synchronized(_gather[0])
765 {
766 _gather[0].position(buffer.getIndex());
767 _gather[0].limit(buffer.putIndex());
768
769 int consumed=0;
770 synchronized(_outBuffer)
771 {
772 try
773 {
774 _outNIOBuffer.clear();
775 _outBuffer.position(0);
776 _outBuffer.limit(_outBuffer.capacity());
777 _result=null;
778 _result=_engine.wrap(_gather[0],_outBuffer);
779 if (_debug) __log.debug(_session+" wrap wrap "+_result);
780 _outNIOBuffer.setGetIndex(0);
781 _outNIOBuffer.setPutIndex(_result.bytesProduced());
782 consumed=_result.bytesConsumed();
783 }
784 finally
785 {
786 _outBuffer.position(0);
787
788 if (consumed>0)
789 {
790 int len=consumed<buffer.length()?consumed:buffer.length();
791 buffer.skip(len);
792 consumed-=len;
793 _gather[0].position(0);
794 _gather[0].limit(_gather[0].capacity());
795 }
796 assert consumed==0;
797 }
798 }
799 }
800 switch(_result.getStatus())
801 {
802 case BUFFER_OVERFLOW:
803 case BUFFER_UNDERFLOW:
804 Log.warn("unwrap {}",_result);
805
806 case OK:
807 return _result.bytesConsumed();
808 case CLOSED:
809 _closing=true;
810 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
811
812 default:
813 Log.warn("wrap "+_result);
814 throw new IOException(_result.toString());
815 }
816 }
817
818 /* ------------------------------------------------------------ */
819 public boolean isBufferingInput()
820 {
821 return _inNIOBuffer.hasContent();
822 }
823
824 /* ------------------------------------------------------------ */
825 public boolean isBufferingOutput()
826 {
827 return _outNIOBuffer.hasContent();
828 }
829
830 /* ------------------------------------------------------------ */
831 public boolean isBufferred()
832 {
833 return true;
834 }
835
836 /* ------------------------------------------------------------ */
837 public SSLEngine getSSLEngine()
838 {
839 return _engine;
840 }
841
842 /* ------------------------------------------------------------ */
843 public String toString()
844 {
845 return super.toString()+","+_engine.getHandshakeStatus()+", in/out="+_inNIOBuffer.length()+"/"+_outNIOBuffer.length()+" "+_result;
846 }
847 }