View Javadoc

1   // ========================================================================
2   // Copyright 2004-2008 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.security;
16  
17  import java.io.IOException;
18  import java.nio.ByteBuffer;
19  import java.nio.channels.SelectionKey;
20  import java.nio.channels.SocketChannel;
21  
22  import javax.net.ssl.SSLEngine;
23  import javax.net.ssl.SSLEngineResult;
24  import javax.net.ssl.SSLException;
25  import javax.net.ssl.SSLSession;
26  import javax.net.ssl.SSLEngineResult.HandshakeStatus;
27  
28  import org.mortbay.io.Buffer;
29  import org.mortbay.io.Buffers;
30  import org.mortbay.io.nio.NIOBuffer;
31  import org.mortbay.io.nio.SelectChannelEndPoint;
32  import org.mortbay.io.nio.SelectorManager;
33  import org.mortbay.jetty.EofException;
34  import org.mortbay.jetty.nio.SelectChannelConnector;
35  import org.mortbay.log.Log;
36  
37  /* ------------------------------------------------------------ */
38  /**
39   * SslHttpChannelEndPoint.
40   * 
41   * @author Nik Gonzalez <ngonzalez@exist.com>
42   * @author Greg Wilkins <gregw@mortbay.com>
43   */
44  public class SslHttpChannelEndPoint extends SelectChannelConnector.ConnectorEndPoint implements Runnable
45  {
46      private static final ByteBuffer[] __NO_BUFFERS={};
47  
48      private Buffers _buffers;
49      
50      private SSLEngine _engine;
51      private ByteBuffer _inBuffer;
52      private NIOBuffer _inNIOBuffer;
53      private ByteBuffer _outBuffer;
54      private NIOBuffer _outNIOBuffer;
55      
56      private ByteBuffer[] _gather=new ByteBuffer[2];
57  
58      private boolean _closing=false;
59      private SSLEngineResult _result;
60      
61      private boolean _handshook=false;
62      private boolean _allowRenegotiate=false;
63  
64  
65      // ssl
66      protected SSLSession _session;
67      
68      /* ------------------------------------------------------------ */
69      public SslHttpChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine)
70              throws SSLException, IOException
71      {
72          super(channel,selectSet,key);
73          _buffers=buffers;
74          
75          // ssl
76          _engine=engine;
77          _session=engine.getSession();
78  
79          // TODO pool buffers and use only when needed.
80          _outNIOBuffer=(NIOBuffer)buffers.getBuffer(_session.getPacketBufferSize());
81          _outBuffer=_outNIOBuffer.getByteBuffer();
82          _inNIOBuffer=(NIOBuffer)buffers.getBuffer(_session.getPacketBufferSize());
83          _inBuffer=_inNIOBuffer.getByteBuffer();
84      }
85  
86  
87      /* ------------------------------------------------------------ */
88      /**
89       * @return True if SSL re-negotiation is allowed (default false)
90       */
91      public boolean isAllowRenegotiate()
92      {
93          return _allowRenegotiate;
94      }
95  
96      /* ------------------------------------------------------------ */
97      /**
98       * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
99       * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
100      * does not have CVE-2009-3555 fixed, then re-negotiation should 
101      * not be allowed.
102      * @param allowRenegotiate true if re-negotiation is allowed (default false)
103      */
104     public void setAllowRenegotiate(boolean allowRenegotiate)
105     {
106         _allowRenegotiate = allowRenegotiate;
107     }
108 
109     /* ------------------------------------------------------------ */
110     // TODO get rid of these dumps
111     public void dump()
112     {
113         System.err.println(_result);
114         // System.err.println(h.toString());
115         // System.err.println("--");
116     }
117     
118     /* ------------------------------------------------------------ */
119     /* (non-Javadoc)
120      * @see org.mortbay.io.nio.SelectChannelEndPoint#idleExpired()
121      */
122     protected void idleExpired()
123     {
124         try
125         {
126             _selectSet.getManager().dispatch(new Runnable()
127             {
128                 public void run() 
129                 { 
130                     doIdleExpired();
131                 }
132             });
133         }
134         catch(Exception e)
135         {
136             Log.ignore(e);
137         }
138     }
139     
140     /* ------------------------------------------------------------ */
141     protected void doIdleExpired()
142     {
143         super.idleExpired();
144     }
145 
146     /* ------------------------------------------------------------ */
147     public void close() throws IOException
148     {
149         // TODO - this really should not be done in a loop here - but with async callbacks.
150 
151         _closing=true;
152         long end=System.currentTimeMillis()+((SocketChannel)_channel).socket().getSoTimeout();
153         try
154         {   
155             if (isBufferingOutput())
156             {
157                 flush();
158                 while (isOpen() && isBufferingOutput() && System.currentTimeMillis()<end)
159                 {
160                     Thread.sleep(100); // TODO non blocking
161                     flush();
162                 }
163             }
164 
165             _engine.closeOutbound();
166 
167             loop: while (isOpen() && !(_engine.isInboundDone() && _engine.isOutboundDone()) && System.currentTimeMillis()<end)
168             {   
169                 if (isBufferingOutput())
170                 {
171                     flush();
172                     while (isOpen() && isBufferingOutput() && System.currentTimeMillis()<end)
173                     {
174                         Thread.sleep(100); // TODO non blocking
175                         flush();
176                     }
177                 }
178 
179                 // System.err.println(_channel+" close "+_engine.getHandshakeStatus()+" "+_closing);
180                 switch(_engine.getHandshakeStatus())
181                 {
182                     case FINISHED:
183                     case NOT_HANDSHAKING:
184                         _handshook=true;
185                         break loop;
186                         
187                     case NEED_UNWRAP:
188                         Buffer buffer =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
189                         try
190                         {
191                             ByteBuffer bbuffer = ((NIOBuffer)buffer).getByteBuffer();
192                             if (!unwrap(bbuffer) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
193                             {
194                                 break loop;
195                             }
196                         }
197                         catch(SSLException e)
198                         {
199                             Log.ignore(e);
200                         }
201                         finally
202                         {
203                             _buffers.returnBuffer(buffer);
204                         }
205                         break;
206                         
207                     case NEED_TASK:
208                     {
209                         Runnable task;
210                         while ((task=_engine.getDelegatedTask())!=null)
211                         {
212                             task.run();
213                         }
214                         break;
215                     }
216                         
217                     case NEED_WRAP:
218                     {
219                         try
220                         {
221                             _outNIOBuffer.compact();
222                             int put=_outNIOBuffer.putIndex();
223                             _outBuffer.position(put);
224                             _result=null;
225                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
226                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
227                         }
228                         finally
229                         {
230                             _outBuffer.position(0);
231                         }
232                         
233                         break;
234                     }
235                 }
236             }
237         }
238         catch(IOException e)
239         {
240             Log.ignore(e);
241         }
242         catch (InterruptedException e)
243         {
244             Log.ignore(e);
245         }
246         finally
247         {
248             super.close();
249             
250             if (_inNIOBuffer!=null)
251                 _buffers.returnBuffer(_inNIOBuffer);
252             if (_outNIOBuffer!=null)
253                 _buffers.returnBuffer(_outNIOBuffer);
254         }   
255     }
256 
257 
258     
259     /* ------------------------------------------------------------ */
260     /* 
261      */
262     public int fill(Buffer buffer) throws IOException
263     {
264         ByteBuffer bbuf=extractInputBuffer(buffer);
265         int size=buffer.length();
266         HandshakeStatus initialStatus = _engine.getHandshakeStatus();
267         synchronized (bbuf)
268         {
269             try
270             {
271                 unwrap(bbuf);
272 
273                 int wraps=0;
274                 loop: while (true)
275                 {
276                     if (isBufferingOutput())
277                     {
278                         flush();
279                         if (isBufferingOutput())
280                             break loop;
281                     }
282 
283                     // System.err.println(_channel+" fill  "+_engine.getHandshakeStatus()+" "+_closing);
284                     switch(_engine.getHandshakeStatus())
285                     {
286                         case FINISHED:
287                         case NOT_HANDSHAKING:
288                             if (_closing)
289                                 return -1;
290                             break loop;
291 
292                         case NEED_UNWRAP:
293                             checkRenegotiate();
294                             if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
295                             {
296                                 break loop;
297                             }
298                             break;
299 
300                         case NEED_TASK:
301                         {
302                             Runnable task;
303                             while ((task=_engine.getDelegatedTask())!=null)
304                             {
305                                 task.run();
306                             }
307                             
308                             if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && 
309                                _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0)
310                             {
311                                 // This should be NEED_WRAP
312                                 // 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.
313                                 // 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.
314                                 // See http://jira.codehaus.org/browse/JETTY-567 for more details
315                                 return -1;
316                             }
317                             break;
318                         }
319 
320                         case NEED_WRAP:
321                         {
322                             checkRenegotiate();
323                             wraps++;
324                             synchronized(_outBuffer)
325                             {
326                                 try
327                                 {
328                                     _outNIOBuffer.compact();
329                                     int put=_outNIOBuffer.putIndex();
330                                     _outBuffer.position();
331                                     _result=null;
332                                     _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
333                                     switch(_result.getStatus())
334                                     {
335                                         case BUFFER_OVERFLOW:
336                                         case BUFFER_UNDERFLOW:
337                                             Log.warn("wrap {}",_result);
338                                         case CLOSED:
339                                             _closing=true;
340                                     }
341                                     
342                                     _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
343                                 }
344                                 finally
345                                 {
346                                     _outBuffer.position(0);
347                                 }
348                             }
349 
350                             flush();
351 
352                             break;
353                         }
354                     }
355                 }
356             }
357             catch(SSLException e)
358             {
359                 Log.warn(e.toString());
360                 Log.debug(e);
361                 throw e;
362             }
363             finally
364             {
365                 buffer.setPutIndex(bbuf.position());
366                 bbuf.position(0);
367             }
368             
369             int filled=buffer.length()-size; 
370             if (filled>0)
371                 _handshook=true;
372             return filled; 
373         }
374         
375     }
376 
377     /* ------------------------------------------------------------ */
378     public int flush(Buffer buffer) throws IOException
379     {
380         return flush(buffer,null,null);
381     }
382 
383 
384     /* ------------------------------------------------------------ */
385     /*     
386      */
387     public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
388     {   
389         int consumed=0;
390         int available=header.length();
391         if (buffer!=null)
392             available+=buffer.length();
393         
394         int tries=0;
395         loop: while (true)
396         {   
397             if (_outNIOBuffer.length()>0)
398             {
399                 flush();
400                 if (isBufferingOutput())
401                     break loop;
402             }
403 
404             // System.err.println(_channel+" flush "+_engine.getHandshakeStatus()+" "+_closing);
405             switch(_engine.getHandshakeStatus())
406             {
407                 case FINISHED:
408                 case NOT_HANDSHAKING:
409                     if (_closing || available==0)
410                     {
411                         if (consumed==0)
412                             consumed= -1;
413                         break loop;
414                     }
415                         
416                     int c;
417                     if (header!=null && header.length()>0)
418                     {
419                         if (buffer!=null && buffer.length()>0)
420                             c=wrap(header,buffer);
421                         else
422                             c=wrap(header);
423                     }
424                     else 
425                         c=wrap(buffer);
426                     
427                     if (c>0)
428                     {
429                         _handshook=true;
430                         consumed+=c;
431                         available-=c;
432                     }
433                     else if (c<0)
434                     {
435                         if (consumed==0)
436                             consumed=-1;
437                         break loop;
438                     }
439                     
440                     break;
441 
442                 case NEED_UNWRAP:
443                     checkRenegotiate();
444                     Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
445                     try
446                     {
447                         ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer();
448                         if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
449                         {
450                             break loop;
451                         }
452                     }
453                     finally
454                     {
455                         _buffers.returnBuffer(buf);
456                     }
457                     
458                     break;
459 
460                 case NEED_TASK:
461                 {
462                     Runnable task;
463                     while ((task=_engine.getDelegatedTask())!=null)
464                     {
465                         task.run();
466                     }
467                     break;
468                 }
469 
470                 case NEED_WRAP:
471                 {
472                     checkRenegotiate();
473                     synchronized(_outBuffer)
474                     {
475                         try
476                         {
477                             _outNIOBuffer.compact();
478                             int put=_outNIOBuffer.putIndex();
479                             _outBuffer.position();
480                             _result=null;
481                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
482                             switch(_result.getStatus())
483                             {
484                                 case BUFFER_OVERFLOW:
485                                 case BUFFER_UNDERFLOW:
486                                     Log.warn("wrap {}",_result);
487                                 case CLOSED:
488                                     _closing=true;
489                             }
490                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
491                         }
492                         finally
493                         {
494                             _outBuffer.position(0);
495                         }
496                     }
497 
498                     flush();
499                     if (isBufferingOutput())
500                         break loop;
501 
502                     break;
503                 }
504             }
505         }
506         
507         return consumed;
508     }
509     
510     /* ------------------------------------------------------------ */
511     public void flush() throws IOException
512     {
513         int len=_outNIOBuffer.length();
514         if (len>0)
515         {
516             int flushed=super.flush(_outNIOBuffer);
517             len=_outNIOBuffer.length();
518             
519             if (len>0)
520             {
521                 Thread.yield();
522                 flushed=super.flush(_outNIOBuffer);
523                 len=_outNIOBuffer.length();
524             }
525         }
526     }
527     
528     /* ------------------------------------------------------------ */
529     private void checkRenegotiate() throws IOException
530     {
531         if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen())
532         {
533             Log.warn("SSL renegotiate denied: "+_channel);
534             close();
535         }   
536     }
537 
538     /* ------------------------------------------------------------ */
539     private ByteBuffer extractInputBuffer(Buffer buffer)
540     {
541         assert buffer instanceof NIOBuffer;
542         NIOBuffer nbuf=(NIOBuffer)buffer;
543         ByteBuffer bbuf=nbuf.getByteBuffer();
544         bbuf.position(buffer.putIndex());
545         return bbuf;
546     }
547 
548     /* ------------------------------------------------------------ */
549     /**
550      * @return true if progress is made
551      */
552     private boolean unwrap(ByteBuffer buffer) throws IOException
553     {
554         if (_inNIOBuffer.hasContent())
555             _inNIOBuffer.compact();
556         else 
557             _inNIOBuffer.clear();
558 
559         int total_filled=0;
560         
561         while (_inNIOBuffer.space()>0 && super.isOpen())
562         {
563             try
564             {
565                 int filled=super.fill(_inNIOBuffer);
566                 if (filled<=0)
567                     break;
568                 total_filled+=filled;
569             }
570             catch(IOException e)
571             {
572                 if (_inNIOBuffer.length()==0)
573                 {
574                     _outNIOBuffer.clear();
575                     throw e;
576                 }
577                 break;
578             }
579         }
580         
581         if (total_filled==0 && _inNIOBuffer.length()==0)
582         {
583             if(!isOpen())
584             {
585                 _outNIOBuffer.clear();
586                 throw new EofException();
587             }
588             return false;
589         }
590 
591         try
592         {
593             _inBuffer.position(_inNIOBuffer.getIndex());
594             _inBuffer.limit(_inNIOBuffer.putIndex());
595             _result=null;
596             _result=_engine.unwrap(_inBuffer,buffer);
597             _inNIOBuffer.skip(_result.bytesConsumed());
598         }
599         finally
600         {
601             _inBuffer.position(0);
602             _inBuffer.limit(_inBuffer.capacity());
603         }
604         
605         switch(_result.getStatus())
606         {
607             case BUFFER_OVERFLOW:
608                 throw new IllegalStateException(_result.toString());                        
609                 
610             case BUFFER_UNDERFLOW:
611                 if (Log.isDebugEnabled()) 
612                     Log.debug("unwrap {}",_result);
613                 if(!isOpen())
614                 {
615                     _inNIOBuffer.clear();
616                     _outNIOBuffer.clear();
617                     throw new EofException();
618                 }
619                 return (total_filled > 0);
620                 
621             case CLOSED:
622                 _closing=true;
623                 
624             case OK:
625                 boolean progress=total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; 
626                 return progress;
627             default:
628                 Log.warn("unwrap "+_result);
629                 throw new IOException(_result.toString());
630         }
631     }
632 
633     /* ------------------------------------------------------------ */
634     private ByteBuffer extractOutputBuffer(Buffer buffer,int n)
635     {
636         if (buffer.buffer() instanceof NIOBuffer)
637             return ((NIOBuffer)buffer.buffer()).getByteBuffer();
638         
639         return ByteBuffer.wrap(buffer.array());
640     }
641 
642     /* ------------------------------------------------------------ */
643     private int wrap(Buffer header, Buffer buffer) throws IOException
644     {
645         _gather[0]=extractOutputBuffer(header,0);
646         synchronized(_gather[0])
647         {
648             _gather[0].position(header.getIndex());
649             _gather[0].limit(header.putIndex());
650 
651             _gather[1]=extractOutputBuffer(buffer,1);
652 
653             synchronized(_gather[1])
654             {
655                 _gather[1].position(buffer.getIndex());
656                 _gather[1].limit(buffer.putIndex());
657 
658                 synchronized(_outBuffer)
659                 {
660                     int consumed=0;
661                     try
662                     {
663                         _outNIOBuffer.clear();
664                         _outBuffer.position(0);
665                         _outBuffer.limit(_outBuffer.capacity());
666 
667                         _result=null;
668                         _result=_engine.wrap(_gather,_outBuffer);
669                         _outNIOBuffer.setGetIndex(0);
670                         _outNIOBuffer.setPutIndex(_result.bytesProduced());
671                         consumed=_result.bytesConsumed();
672                     }
673                     finally
674                     {
675                         _outBuffer.position(0);
676 
677                         if (consumed>0 && header!=null)
678                         {
679                             int len=consumed<header.length()?consumed:header.length();
680                             header.skip(len);
681                             consumed-=len;
682                             _gather[0].position(0);
683                             _gather[0].limit(_gather[0].capacity());
684                         }
685                         if (consumed>0 && buffer!=null)
686                         {
687                             int len=consumed<buffer.length()?consumed:buffer.length();
688                             buffer.skip(len);
689                             consumed-=len;
690                             _gather[1].position(0);
691                             _gather[1].limit(_gather[1].capacity());
692                         }
693                         assert consumed==0;
694                     }
695                 }
696             }
697         }
698         
699 
700         switch(_result.getStatus())
701         {
702             case BUFFER_OVERFLOW:
703             case BUFFER_UNDERFLOW:
704                 Log.warn("wrap {}",_result);
705                 
706             case OK:
707                 return _result.bytesConsumed();
708             case CLOSED:
709                 _closing=true;
710                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
711 
712             default:
713                 Log.warn("wrap "+_result);
714             throw new IOException(_result.toString());
715         }
716     }
717 
718     /* ------------------------------------------------------------ */
719     private int wrap(Buffer buffer) throws IOException
720     {
721         _gather[0]=extractOutputBuffer(buffer,0);
722         synchronized(_gather[0])
723         {
724             _gather[0].position(buffer.getIndex());
725             _gather[0].limit(buffer.putIndex());
726 
727             int consumed=0;
728             synchronized(_outBuffer)
729             {
730                 try
731                 {
732                     _outNIOBuffer.clear();
733                     _outBuffer.position(0);
734                     _outBuffer.limit(_outBuffer.capacity());
735                     _result=null;
736                     _result=_engine.wrap(_gather[0],_outBuffer);
737                     _outNIOBuffer.setGetIndex(0);
738                     _outNIOBuffer.setPutIndex(_result.bytesProduced());
739                     consumed=_result.bytesConsumed();
740                 }
741                 finally
742                 {
743                     _outBuffer.position(0);
744 
745                     if (consumed>0 && buffer!=null)
746                     {
747                         int len=consumed<buffer.length()?consumed:buffer.length();
748                         buffer.skip(len);
749                         consumed-=len;
750                         _gather[0].position(0);
751                         _gather[0].limit(_gather[0].capacity());
752                     }
753                     assert consumed==0;
754                 }
755             }
756         }
757         switch(_result.getStatus())
758         {
759             case BUFFER_OVERFLOW:
760             case BUFFER_UNDERFLOW:
761                 Log.warn("wrap {}",_result);
762                 
763             case OK:
764                 return _result.bytesConsumed();
765             case CLOSED:
766                 _closing=true;
767                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
768 
769             default:
770                 Log.warn("wrap "+_result);
771             throw new IOException(_result.toString());
772         }
773     }
774 
775     /* ------------------------------------------------------------ */
776     public boolean isBufferingInput()
777     {
778         return _inNIOBuffer.hasContent();
779     }
780 
781     /* ------------------------------------------------------------ */
782     public boolean isBufferingOutput()
783     {
784         return _outNIOBuffer.hasContent();
785     }
786 
787     /* ------------------------------------------------------------ */
788     public boolean isBufferred()
789     {
790         return true;
791     }
792 
793     /* ------------------------------------------------------------ */
794     public SSLEngine getSSLEngine()
795     {
796         return _engine;
797     }
798 
799     /* ------------------------------------------------------------ */
800     public String toString()
801     {
802         return super.toString()+","+_engine.getHandshakeStatus()+", in/out="+_inNIOBuffer.length()+"/"+_outNIOBuffer.length()+" "+_result;
803     }
804 }