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 shutdownOutput() throws IOException
148     {
149         close();
150     }
151     
152     /* ------------------------------------------------------------ */
153     public void close() throws IOException
154     {
155         // TODO - this really should not be done in a loop here - but with async callbacks.
156 
157         _closing=true;
158         long end=System.currentTimeMillis()+((SocketChannel)_channel).socket().getSoTimeout();
159         try
160         {   
161             if (isBufferingOutput())
162             {
163                 flush();
164                 while (isOpen() && isBufferingOutput() && System.currentTimeMillis()<end)
165                 {
166                     Thread.sleep(100); // TODO non blocking
167                     flush();
168                 }
169             }
170 
171             _engine.closeOutbound();
172 
173             loop: while (isOpen() && !(_engine.isInboundDone() && _engine.isOutboundDone()) && System.currentTimeMillis()<end)
174             {   
175                 if (isBufferingOutput())
176                 {
177                     flush();
178                     while (isOpen() && isBufferingOutput() && System.currentTimeMillis()<end)
179                     {
180                         Thread.sleep(100); // TODO non blocking
181                         flush();
182                     }
183                 }
184 
185                 // System.err.println(_channel+" close "+_engine.getHandshakeStatus()+" "+_closing);
186                 switch(_engine.getHandshakeStatus())
187                 {
188                     case FINISHED:
189                     case NOT_HANDSHAKING:
190                         _handshook=true;
191                         break loop;
192                         
193                     case NEED_UNWRAP:
194                         Buffer buffer =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
195                         try
196                         {
197                             ByteBuffer bbuffer = ((NIOBuffer)buffer).getByteBuffer();
198                             if (!unwrap(bbuffer) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
199                             {
200                                 break loop;
201                             }
202                         }
203                         catch(SSLException e)
204                         {
205                             Log.ignore(e);
206                         }
207                         finally
208                         {
209                             _buffers.returnBuffer(buffer);
210                         }
211                         break;
212                         
213                     case NEED_TASK:
214                     {
215                         Runnable task;
216                         while ((task=_engine.getDelegatedTask())!=null)
217                         {
218                             task.run();
219                         }
220                         break;
221                     }
222                         
223                     case NEED_WRAP:
224                     {
225                         try
226                         {
227                             _outNIOBuffer.compact();
228                             int put=_outNIOBuffer.putIndex();
229                             _outBuffer.position(put);
230                             _result=null;
231                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
232                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
233                         }
234                         finally
235                         {
236                             _outBuffer.position(0);
237                         }
238                         
239                         break;
240                     }
241                 }
242             }
243         }
244         catch(IOException e)
245         {
246             Log.ignore(e);
247         }
248         catch (InterruptedException e)
249         {
250             Log.ignore(e);
251         }
252         finally
253         {
254             super.close();
255             
256             if (_inNIOBuffer!=null)
257                 _buffers.returnBuffer(_inNIOBuffer);
258             if (_outNIOBuffer!=null)
259                 _buffers.returnBuffer(_outNIOBuffer);
260         }   
261     }
262 
263 
264     
265     /* ------------------------------------------------------------ */
266     /* 
267      */
268     public int fill(Buffer buffer) throws IOException
269     {
270         ByteBuffer bbuf=extractInputBuffer(buffer);
271         int size=buffer.length();
272         HandshakeStatus initialStatus = _engine.getHandshakeStatus();
273         synchronized (bbuf)
274         {
275             try
276             {
277                 unwrap(bbuf);
278 
279                 int wraps=0;
280                 loop: while (true)
281                 {
282                     if (isBufferingOutput())
283                     {
284                         flush();
285                         if (isBufferingOutput())
286                             break loop;
287                     }
288 
289                     // System.err.println(_channel+" fill  "+_engine.getHandshakeStatus()+" "+_closing);
290                     switch(_engine.getHandshakeStatus())
291                     {
292                         case FINISHED:
293                         case NOT_HANDSHAKING:
294                             if (_closing)
295                                 return -1;
296                             break loop;
297 
298                         case NEED_UNWRAP:
299                             checkRenegotiate();
300                             if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
301                             {
302                                 break loop;
303                             }
304                             break;
305 
306                         case NEED_TASK:
307                         {
308                             Runnable task;
309                             while ((task=_engine.getDelegatedTask())!=null)
310                             {
311                                 task.run();
312                             }
313                             
314                             if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && 
315                                _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0)
316                             {
317                                 // This should be NEED_WRAP
318                                 // 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.
319                                 // 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.
320                                 // See http://jira.codehaus.org/browse/JETTY-567 for more details
321                                 return -1;
322                             }
323                             break;
324                         }
325 
326                         case NEED_WRAP:
327                         {
328                             checkRenegotiate();
329                             wraps++;
330                             synchronized(_outBuffer)
331                             {
332                                 try
333                                 {
334                                     _outNIOBuffer.compact();
335                                     int put=_outNIOBuffer.putIndex();
336                                     _outBuffer.position();
337                                     _result=null;
338                                     _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
339                                     switch(_result.getStatus())
340                                     {
341                                         case BUFFER_OVERFLOW:
342                                         case BUFFER_UNDERFLOW:
343                                             Log.warn("wrap {}",_result);
344                                         case CLOSED:
345                                             _closing=true;
346                                     }
347                                     
348                                     _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
349                                 }
350                                 finally
351                                 {
352                                     _outBuffer.position(0);
353                                 }
354                             }
355 
356                             flush();
357 
358                             break;
359                         }
360                     }
361                 }
362             }
363             catch(SSLException e)
364             {
365                 Log.warn(e.toString());
366                 Log.debug(e);
367                 throw e;
368             }
369             finally
370             {
371                 buffer.setPutIndex(bbuf.position());
372                 bbuf.position(0);
373             }
374             
375             int filled=buffer.length()-size; 
376             if (filled>0)
377                 _handshook=true;
378             return filled; 
379         }
380         
381     }
382 
383     /* ------------------------------------------------------------ */
384     public int flush(Buffer buffer) throws IOException
385     {
386         return flush(buffer,null,null);
387     }
388 
389 
390     /* ------------------------------------------------------------ */
391     /*     
392      */
393     public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
394     {   
395         int consumed=0;
396         int available=header.length();
397         if (buffer!=null)
398             available+=buffer.length();
399         
400         int tries=0;
401         loop: while (true)
402         {   
403             if (_outNIOBuffer.length()>0)
404             {
405                 flush();
406                 if (isBufferingOutput())
407                     break loop;
408             }
409 
410             // System.err.println(_channel+" flush "+_engine.getHandshakeStatus()+" "+_closing);
411             switch(_engine.getHandshakeStatus())
412             {
413                 case FINISHED:
414                 case NOT_HANDSHAKING:
415                     if (_closing || available==0)
416                     {
417                         if (consumed==0)
418                             consumed= -1;
419                         break loop;
420                     }
421                         
422                     int c;
423                     if (header!=null && header.length()>0)
424                     {
425                         if (buffer!=null && buffer.length()>0)
426                             c=wrap(header,buffer);
427                         else
428                             c=wrap(header);
429                     }
430                     else 
431                         c=wrap(buffer);
432                     
433                     if (c>0)
434                     {
435                         _handshook=true;
436                         consumed+=c;
437                         available-=c;
438                     }
439                     else if (c<0)
440                     {
441                         if (consumed==0)
442                             consumed=-1;
443                         break loop;
444                     }
445                     
446                     break;
447 
448                 case NEED_UNWRAP:
449                     checkRenegotiate();
450                     Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
451                     try
452                     {
453                         ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer();
454                         if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
455                         {
456                             break loop;
457                         }
458                     }
459                     finally
460                     {
461                         _buffers.returnBuffer(buf);
462                     }
463                     
464                     break;
465 
466                 case NEED_TASK:
467                 {
468                     Runnable task;
469                     while ((task=_engine.getDelegatedTask())!=null)
470                     {
471                         task.run();
472                     }
473                     break;
474                 }
475 
476                 case NEED_WRAP:
477                 {
478                     checkRenegotiate();
479                     synchronized(_outBuffer)
480                     {
481                         try
482                         {
483                             _outNIOBuffer.compact();
484                             int put=_outNIOBuffer.putIndex();
485                             _outBuffer.position();
486                             _result=null;
487                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
488                             switch(_result.getStatus())
489                             {
490                                 case BUFFER_OVERFLOW:
491                                 case BUFFER_UNDERFLOW:
492                                     Log.warn("wrap {}",_result);
493                                 case CLOSED:
494                                     _closing=true;
495                             }
496                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
497                         }
498                         finally
499                         {
500                             _outBuffer.position(0);
501                         }
502                     }
503 
504                     flush();
505                     if (isBufferingOutput())
506                         break loop;
507 
508                     break;
509                 }
510             }
511         }
512         
513         return consumed;
514     }
515     
516     /* ------------------------------------------------------------ */
517     public void flush() throws IOException
518     {
519         int len=_outNIOBuffer.length();
520         if (len>0)
521         {
522             int flushed=super.flush(_outNIOBuffer);
523             len=_outNIOBuffer.length();
524             
525             if (len>0)
526             {
527                 Thread.yield();
528                 flushed=super.flush(_outNIOBuffer);
529                 len=_outNIOBuffer.length();
530             }
531         }
532     }
533     
534     /* ------------------------------------------------------------ */
535     private void checkRenegotiate() throws IOException
536     {
537         if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen())
538         {
539             Log.warn("SSL renegotiate denied: "+_channel);
540             close();
541         }   
542     }
543 
544     /* ------------------------------------------------------------ */
545     private ByteBuffer extractInputBuffer(Buffer buffer)
546     {
547         assert buffer instanceof NIOBuffer;
548         NIOBuffer nbuf=(NIOBuffer)buffer;
549         ByteBuffer bbuf=nbuf.getByteBuffer();
550         bbuf.position(buffer.putIndex());
551         return bbuf;
552     }
553 
554     /* ------------------------------------------------------------ */
555     /**
556      * @return true if progress is made
557      */
558     private boolean unwrap(ByteBuffer buffer) throws IOException
559     {
560         if (_inNIOBuffer.hasContent())
561             _inNIOBuffer.compact();
562         else 
563             _inNIOBuffer.clear();
564 
565         int total_filled=0;
566         
567         while (_inNIOBuffer.space()>0 && super.isOpen())
568         {
569             try
570             {
571                 int filled=super.fill(_inNIOBuffer);
572                 if (filled<=0)
573                     break;
574                 total_filled+=filled;
575             }
576             catch(IOException e)
577             {
578                 if (_inNIOBuffer.length()==0)
579                 {
580                     _outNIOBuffer.clear();
581                     throw e;
582                 }
583                 break;
584             }
585         }
586         
587         if (total_filled==0 && _inNIOBuffer.length()==0)
588         {
589             if(!isOpen())
590             {
591                 _outNIOBuffer.clear();
592                 throw new EofException();
593             }
594             return false;
595         }
596 
597         try
598         {
599             _inBuffer.position(_inNIOBuffer.getIndex());
600             _inBuffer.limit(_inNIOBuffer.putIndex());
601             _result=null;
602             _result=_engine.unwrap(_inBuffer,buffer);
603             _inNIOBuffer.skip(_result.bytesConsumed());
604         }
605         finally
606         {
607             _inBuffer.position(0);
608             _inBuffer.limit(_inBuffer.capacity());
609         }
610         
611         switch(_result.getStatus())
612         {
613             case BUFFER_OVERFLOW:
614                 throw new IllegalStateException(_result.toString());                        
615                 
616             case BUFFER_UNDERFLOW:
617                 if (Log.isDebugEnabled()) 
618                     Log.debug("unwrap {}",_result);
619                 if(!isOpen())
620                 {
621                     _inNIOBuffer.clear();
622                     _outNIOBuffer.clear();
623                     throw new EofException();
624                 }
625                 return (total_filled > 0);
626                 
627             case CLOSED:
628                 _closing=true;
629                 
630             case OK:
631                 boolean progress=total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; 
632                 return progress;
633             default:
634                 Log.warn("unwrap "+_result);
635                 throw new IOException(_result.toString());
636         }
637     }
638 
639     /* ------------------------------------------------------------ */
640     private ByteBuffer extractOutputBuffer(Buffer buffer,int n)
641     {
642         if (buffer.buffer() instanceof NIOBuffer)
643             return ((NIOBuffer)buffer.buffer()).getByteBuffer();
644         
645         return ByteBuffer.wrap(buffer.array());
646     }
647 
648     /* ------------------------------------------------------------ */
649     private int wrap(Buffer header, Buffer buffer) throws IOException
650     {
651         _gather[0]=extractOutputBuffer(header,0);
652         synchronized(_gather[0])
653         {
654             _gather[0].position(header.getIndex());
655             _gather[0].limit(header.putIndex());
656 
657             _gather[1]=extractOutputBuffer(buffer,1);
658 
659             synchronized(_gather[1])
660             {
661                 _gather[1].position(buffer.getIndex());
662                 _gather[1].limit(buffer.putIndex());
663 
664                 synchronized(_outBuffer)
665                 {
666                     int consumed=0;
667                     try
668                     {
669                         _outNIOBuffer.clear();
670                         _outBuffer.position(0);
671                         _outBuffer.limit(_outBuffer.capacity());
672 
673                         _result=null;
674                         _result=_engine.wrap(_gather,_outBuffer);
675                         _outNIOBuffer.setGetIndex(0);
676                         _outNIOBuffer.setPutIndex(_result.bytesProduced());
677                         consumed=_result.bytesConsumed();
678                     }
679                     finally
680                     {
681                         _outBuffer.position(0);
682 
683                         if (consumed>0 && header!=null)
684                         {
685                             int len=consumed<header.length()?consumed:header.length();
686                             header.skip(len);
687                             consumed-=len;
688                             _gather[0].position(0);
689                             _gather[0].limit(_gather[0].capacity());
690                         }
691                         if (consumed>0 && buffer!=null)
692                         {
693                             int len=consumed<buffer.length()?consumed:buffer.length();
694                             buffer.skip(len);
695                             consumed-=len;
696                             _gather[1].position(0);
697                             _gather[1].limit(_gather[1].capacity());
698                         }
699                         assert consumed==0;
700                     }
701                 }
702             }
703         }
704         
705 
706         switch(_result.getStatus())
707         {
708             case BUFFER_OVERFLOW:
709             case BUFFER_UNDERFLOW:
710                 Log.warn("wrap {}",_result);
711                 
712             case OK:
713                 return _result.bytesConsumed();
714             case CLOSED:
715                 _closing=true;
716                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
717 
718             default:
719                 Log.warn("wrap "+_result);
720             throw new IOException(_result.toString());
721         }
722     }
723 
724     /* ------------------------------------------------------------ */
725     private int wrap(Buffer buffer) throws IOException
726     {
727         _gather[0]=extractOutputBuffer(buffer,0);
728         synchronized(_gather[0])
729         {
730             _gather[0].position(buffer.getIndex());
731             _gather[0].limit(buffer.putIndex());
732 
733             int consumed=0;
734             synchronized(_outBuffer)
735             {
736                 try
737                 {
738                     _outNIOBuffer.clear();
739                     _outBuffer.position(0);
740                     _outBuffer.limit(_outBuffer.capacity());
741                     _result=null;
742                     _result=_engine.wrap(_gather[0],_outBuffer);
743                     _outNIOBuffer.setGetIndex(0);
744                     _outNIOBuffer.setPutIndex(_result.bytesProduced());
745                     consumed=_result.bytesConsumed();
746                 }
747                 finally
748                 {
749                     _outBuffer.position(0);
750 
751                     if (consumed>0 && buffer!=null)
752                     {
753                         int len=consumed<buffer.length()?consumed:buffer.length();
754                         buffer.skip(len);
755                         consumed-=len;
756                         _gather[0].position(0);
757                         _gather[0].limit(_gather[0].capacity());
758                     }
759                     assert consumed==0;
760                 }
761             }
762         }
763         switch(_result.getStatus())
764         {
765             case BUFFER_OVERFLOW:
766             case BUFFER_UNDERFLOW:
767                 Log.warn("wrap {}",_result);
768                 
769             case OK:
770                 return _result.bytesConsumed();
771             case CLOSED:
772                 _closing=true;
773                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
774 
775             default:
776                 Log.warn("wrap "+_result);
777             throw new IOException(_result.toString());
778         }
779     }
780 
781     /* ------------------------------------------------------------ */
782     public boolean isBufferingInput()
783     {
784         return _inNIOBuffer.hasContent();
785     }
786 
787     /* ------------------------------------------------------------ */
788     public boolean isBufferingOutput()
789     {
790         return _outNIOBuffer.hasContent();
791     }
792 
793     /* ------------------------------------------------------------ */
794     public boolean isBufferred()
795     {
796         return true;
797     }
798 
799     /* ------------------------------------------------------------ */
800     public SSLEngine getSSLEngine()
801     {
802         return _engine;
803     }
804 
805     /* ------------------------------------------------------------ */
806     public String toString()
807     {
808         return super.toString()+","+_engine.getHandshakeStatus()+", in/out="+_inNIOBuffer.length()+"/"+_outNIOBuffer.length()+" "+_result;
809     }
810 }