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 NIOBuffer[] _reuseBuffer=new NIOBuffer[2];    
57      private ByteBuffer[] _gather=new ByteBuffer[2];
58  
59      private boolean _closing=false;
60      private SSLEngineResult _result;
61      
62      private boolean _handshook=false;
63      private boolean _allowRenegotiate=false;
64  
65  
66      // ssl
67      protected SSLSession _session;
68      
69      /* ------------------------------------------------------------ */
70      public SslHttpChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine)
71              throws SSLException, IOException
72      {
73          super(channel,selectSet,key);
74          _buffers=buffers;
75          
76          // ssl
77          _engine=engine;
78          _session=engine.getSession();
79  
80          // TODO pool buffers and use only when needed.
81          _outNIOBuffer=(NIOBuffer)buffers.getBuffer(_session.getPacketBufferSize());
82          _outBuffer=_outNIOBuffer.getByteBuffer();
83          _inNIOBuffer=(NIOBuffer)buffers.getBuffer(_session.getPacketBufferSize());
84          _inBuffer=_inNIOBuffer.getByteBuffer();
85      }
86  
87  
88      /* ------------------------------------------------------------ */
89      /**
90       * @return True if SSL re-negotiation is allowed (default false)
91       */
92      public boolean isAllowRenegotiate()
93      {
94          return _allowRenegotiate;
95      }
96  
97      /* ------------------------------------------------------------ */
98      /**
99       * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
100      * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
101      * does not have CVE-2009-3555 fixed, then re-negotiation should 
102      * not be allowed.
103      * @param allowRenegotiate true if re-negotiation is allowed (default false)
104      */
105     public void setAllowRenegotiate(boolean allowRenegotiate)
106     {
107         _allowRenegotiate = allowRenegotiate;
108     }
109 
110     /* ------------------------------------------------------------ */
111     // TODO get rid of these dumps
112     public void dump()
113     {
114         System.err.println(_result);
115         // System.err.println(h.toString());
116         // System.err.println("--");
117     }
118     
119     /* ------------------------------------------------------------ */
120     /* (non-Javadoc)
121      * @see org.mortbay.io.nio.SelectChannelEndPoint#idleExpired()
122      */
123     protected void idleExpired()
124     {
125         try
126         {
127             _selectSet.getManager().dispatch(new Runnable()
128             {
129                 public void run() 
130                 { 
131                     doIdleExpired();
132                 }
133             });
134         }
135         catch(Exception e)
136         {
137             Log.ignore(e);
138         }
139     }
140     
141     /* ------------------------------------------------------------ */
142     protected void doIdleExpired()
143     {
144         super.idleExpired();
145     }
146 
147     /* ------------------------------------------------------------ */
148     public void close() throws IOException
149     {
150         // TODO - this really should not be done in a loop here - but with async callbacks.
151 
152         _closing=true;
153         try
154         {   
155             if (isBufferingOutput())
156             {
157                 flush();
158                 while (isOpen() && isBufferingOutput())
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()))
168             {   
169                 if (isBufferingOutput())
170                 {
171                     flush();
172                     while (isOpen() && isBufferingOutput())
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             if (_reuseBuffer[0]!=null)
255                 _buffers.returnBuffer(_reuseBuffer[0]);
256             if (_reuseBuffer[1]!=null)
257                 _buffers.returnBuffer(_reuseBuffer[1]);
258         }   
259     }
260 
261 
262     
263     /* ------------------------------------------------------------ */
264     /* 
265      */
266     public int fill(Buffer buffer) throws IOException
267     {
268         ByteBuffer bbuf=extractInputBuffer(buffer);
269         int size=buffer.length();
270         HandshakeStatus initialStatus = _engine.getHandshakeStatus();
271         synchronized (bbuf)
272         {
273             try
274             {
275                 unwrap(bbuf);
276 
277                 int wraps=0;
278                 loop: while (true)
279                 {
280                     if (isBufferingOutput())
281                     {
282                         flush();
283                         if (isBufferingOutput())
284                             break loop;
285                     }
286 
287                     // System.err.println(_channel+" fill  "+_engine.getHandshakeStatus()+" "+_closing);
288                     switch(_engine.getHandshakeStatus())
289                     {
290                         case FINISHED:
291                         case NOT_HANDSHAKING:
292                             if (_closing)
293                                 return -1;
294                             break loop;
295 
296                         case NEED_UNWRAP:
297                             checkRenegotiate();
298                             if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
299                             {
300                                 break loop;
301                             }
302                             break;
303 
304                         case NEED_TASK:
305                         {
306                             Runnable task;
307                             while ((task=_engine.getDelegatedTask())!=null)
308                             {
309                                 task.run();
310                             }
311                             
312                             if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && 
313                                _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0)
314                             {
315                                 // This should be NEED_WRAP
316                                 // 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.
317                                 // 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.
318                                 // See http://jira.codehaus.org/browse/JETTY-567 for more details
319                                 return -1;
320                             }
321                             break;
322                         }
323 
324                         case NEED_WRAP:
325                         {
326                             checkRenegotiate();
327                             wraps++;
328                             synchronized(_outBuffer)
329                             {
330                                 try
331                                 {
332                                     _outNIOBuffer.compact();
333                                     int put=_outNIOBuffer.putIndex();
334                                     _outBuffer.position();
335                                     _result=null;
336                                     _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
337                                     switch(_result.getStatus())
338                                     {
339                                         case BUFFER_OVERFLOW:
340                                         case BUFFER_UNDERFLOW:
341                                             Log.warn("wrap {}",_result);
342                                         case CLOSED:
343                                             _closing=true;
344                                     }
345                                     
346                                     _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
347                                 }
348                                 finally
349                                 {
350                                     _outBuffer.position(0);
351                                 }
352                             }
353 
354                             flush();
355 
356                             break;
357                         }
358                     }
359                 }
360             }
361             catch(SSLException e)
362             {
363                 Log.warn(e.toString());
364                 Log.debug(e);
365                 throw e;
366             }
367             finally
368             {
369                 buffer.setPutIndex(bbuf.position());
370                 bbuf.position(0);
371             }
372             
373             int filled=buffer.length()-size; 
374             if (filled>0)
375                 _handshook=true;
376             return filled; 
377         }
378         
379     }
380 
381     /* ------------------------------------------------------------ */
382     public int flush(Buffer buffer) throws IOException
383     {
384         return flush(buffer,null,null);
385     }
386 
387 
388     /* ------------------------------------------------------------ */
389     /*     
390      */
391     public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
392     {   
393         int consumed=0;
394         int available=header.length();
395         if (buffer!=null)
396             available+=buffer.length();
397         
398         int tries=0;
399         loop: while (true)
400         {   
401             if (_outNIOBuffer.length()>0)
402             {
403                 flush();
404                 if (isBufferingOutput())
405                     break loop;
406             }
407 
408             // System.err.println(_channel+" flush "+_engine.getHandshakeStatus()+" "+_closing);
409             switch(_engine.getHandshakeStatus())
410             {
411                 case FINISHED:
412                 case NOT_HANDSHAKING:
413                     if (_closing || available==0)
414                     {
415                         if (consumed==0)
416                             consumed= -1;
417                         break loop;
418                     }
419                         
420                     int c;
421                     if (header!=null && header.length()>0)
422                     {
423                         if (buffer!=null && buffer.length()>0)
424                             c=wrap(header,buffer);
425                         else
426                             c=wrap(header);
427                     }
428                     else 
429                         c=wrap(buffer);
430                     
431                     if (c>0)
432                     {
433                         _handshook=true;
434                         consumed+=c;
435                         available-=c;
436                     }
437                     else if (c<0)
438                     {
439                         if (consumed==0)
440                             consumed=-1;
441                         break loop;
442                     }
443                     
444                     break;
445 
446                 case NEED_UNWRAP:
447                     checkRenegotiate();
448                     Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
449                     try
450                     {
451                         ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer();
452                         if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
453                         {
454                             break loop;
455                         }
456                     }
457                     finally
458                     {
459                         _buffers.returnBuffer(buf);
460                     }
461                     
462                     break;
463 
464                 case NEED_TASK:
465                 {
466                     Runnable task;
467                     while ((task=_engine.getDelegatedTask())!=null)
468                     {
469                         task.run();
470                     }
471                     break;
472                 }
473 
474                 case NEED_WRAP:
475                 {
476                     checkRenegotiate();
477                     synchronized(_outBuffer)
478                     {
479                         try
480                         {
481                             _outNIOBuffer.compact();
482                             int put=_outNIOBuffer.putIndex();
483                             _outBuffer.position();
484                             _result=null;
485                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
486                             switch(_result.getStatus())
487                             {
488                                 case BUFFER_OVERFLOW:
489                                 case BUFFER_UNDERFLOW:
490                                     Log.warn("wrap {}",_result);
491                                 case CLOSED:
492                                     _closing=true;
493                             }
494                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
495                         }
496                         finally
497                         {
498                             _outBuffer.position(0);
499                         }
500                     }
501 
502                     flush();
503                     if (isBufferingOutput())
504                         break loop;
505 
506                     break;
507                 }
508             }
509         }
510         
511         return consumed;
512     }
513     
514     /* ------------------------------------------------------------ */
515     public void flush() throws IOException
516     {
517         int len=_outNIOBuffer.length();
518         if (len>0)
519         {
520             int flushed=super.flush(_outNIOBuffer);
521             len=_outNIOBuffer.length();
522             
523             if (len>0)
524             {
525                 Thread.yield();
526                 flushed=super.flush(_outNIOBuffer);
527                 len=_outNIOBuffer.length();
528             }
529         }
530     }
531     
532     /* ------------------------------------------------------------ */
533     private void checkRenegotiate() throws IOException
534     {
535         if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen())
536         {
537             Log.warn("SSL renegotiate denied: "+_channel);
538             close();
539         }   
540     }
541 
542     /* ------------------------------------------------------------ */
543     private ByteBuffer extractInputBuffer(Buffer buffer)
544     {
545         assert buffer instanceof NIOBuffer;
546         NIOBuffer nbuf=(NIOBuffer)buffer;
547         ByteBuffer bbuf=nbuf.getByteBuffer();
548         bbuf.position(buffer.putIndex());
549         return bbuf;
550     }
551 
552     /* ------------------------------------------------------------ */
553     /**
554      * @return true if progress is made
555      */
556     private boolean unwrap(ByteBuffer buffer) throws IOException
557     {
558         if (_inNIOBuffer.hasContent())
559             _inNIOBuffer.compact();
560         else 
561             _inNIOBuffer.clear();
562 
563         int total_filled=0;
564         
565         while (_inNIOBuffer.space()>0 && super.isOpen())
566         {
567             try
568             {
569                 int filled=super.fill(_inNIOBuffer);
570                 if (filled<=0)
571                     break;
572                 total_filled+=filled;
573             }
574             catch(IOException e)
575             {
576                 if (_inNIOBuffer.length()==0)
577                 {
578                     _outNIOBuffer.clear();
579                     throw e;
580                 }
581                 break;
582             }
583         }
584         
585         if (total_filled==0 && _inNIOBuffer.length()==0)
586         {
587             if(!isOpen())
588             {
589                 _outNIOBuffer.clear();
590                 throw new EofException();
591             }
592             return false;
593         }
594 
595         try
596         {
597             _inBuffer.position(_inNIOBuffer.getIndex());
598             _inBuffer.limit(_inNIOBuffer.putIndex());
599             _result=null;
600             _result=_engine.unwrap(_inBuffer,buffer);
601             _inNIOBuffer.skip(_result.bytesConsumed());
602         }
603         finally
604         {
605             _inBuffer.position(0);
606             _inBuffer.limit(_inBuffer.capacity());
607         }
608         
609         switch(_result.getStatus())
610         {
611             case BUFFER_OVERFLOW:
612                 throw new IllegalStateException(_result.toString());                        
613                 
614             case BUFFER_UNDERFLOW:
615                 if (Log.isDebugEnabled()) 
616                     Log.debug("unwrap {}",_result);
617                 if(!isOpen())
618                 {
619                     _inNIOBuffer.clear();
620                     _outNIOBuffer.clear();
621                     throw new EofException();
622                 }
623                 return (total_filled > 0);
624                 
625             case CLOSED:
626                 _closing=true;
627                 
628             case OK:
629                 boolean progress=total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; 
630                 return progress;
631             default:
632                 Log.warn("unwrap "+_result);
633                 throw new IOException(_result.toString());
634         }
635     }
636 
637     
638     /* ------------------------------------------------------------ */
639     private ByteBuffer extractOutputBuffer(Buffer buffer,int n)
640     {
641         NIOBuffer nBuf=null;
642 
643         if (buffer.buffer() instanceof NIOBuffer)
644         {
645             nBuf=(NIOBuffer)buffer.buffer();
646             return nBuf.getByteBuffer();
647         }
648         else
649         {
650             if (_reuseBuffer[n]==null)
651                 _reuseBuffer[n] = (NIOBuffer)_buffers.getBuffer(_session.getApplicationBufferSize());
652             NIOBuffer buf = _reuseBuffer[n];
653             buf.clear();
654             buf.put(buffer);
655             return buf.getByteBuffer();
656         }
657     }
658 
659     /* ------------------------------------------------------------ */
660     private int wrap(Buffer header, Buffer buffer) throws IOException
661     {
662         _gather[0]=extractOutputBuffer(header,0);
663         synchronized(_gather[0])
664         {
665             _gather[0].position(header.getIndex());
666             _gather[0].limit(header.putIndex());
667 
668             _gather[1]=extractOutputBuffer(buffer,1);
669 
670             synchronized(_gather[1])
671             {
672                 _gather[1].position(buffer.getIndex());
673                 _gather[1].limit(buffer.putIndex());
674 
675                 synchronized(_outBuffer)
676                 {
677                     int consumed=0;
678                     try
679                     {
680                         _outNIOBuffer.clear();
681                         _outBuffer.position(0);
682                         _outBuffer.limit(_outBuffer.capacity());
683 
684                         _result=null;
685                         _result=_engine.wrap(_gather,_outBuffer);
686                         _outNIOBuffer.setGetIndex(0);
687                         _outNIOBuffer.setPutIndex(_result.bytesProduced());
688                         consumed=_result.bytesConsumed();
689                     }
690                     finally
691                     {
692                         _outBuffer.position(0);
693 
694                         if (consumed>0 && header!=null)
695                         {
696                             int len=consumed<header.length()?consumed:header.length();
697                             header.skip(len);
698                             consumed-=len;
699                             _gather[0].position(0);
700                             _gather[0].limit(_gather[0].capacity());
701                         }
702                         if (consumed>0 && buffer!=null)
703                         {
704                             int len=consumed<buffer.length()?consumed:buffer.length();
705                             buffer.skip(len);
706                             consumed-=len;
707                             _gather[1].position(0);
708                             _gather[1].limit(_gather[1].capacity());
709                         }
710                         assert consumed==0;
711                     }
712                 }
713             }
714         }
715         
716 
717         switch(_result.getStatus())
718         {
719             case BUFFER_OVERFLOW:
720             case BUFFER_UNDERFLOW:
721                 Log.warn("wrap {}",_result);
722                 
723             case OK:
724                 return _result.bytesConsumed();
725             case CLOSED:
726                 _closing=true;
727                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
728 
729             default:
730                 Log.warn("wrap "+_result);
731             throw new IOException(_result.toString());
732         }
733     }
734 
735     /* ------------------------------------------------------------ */
736     private int wrap(Buffer buffer) throws IOException
737     {
738         _gather[0]=extractOutputBuffer(buffer,0);
739         synchronized(_gather[0])
740         {
741             _gather[0].position(buffer.getIndex());
742             _gather[0].limit(buffer.putIndex());
743 
744             int consumed=0;
745             synchronized(_outBuffer)
746             {
747                 try
748                 {
749                     _outNIOBuffer.clear();
750                     _outBuffer.position(0);
751                     _outBuffer.limit(_outBuffer.capacity());
752                     _result=null;
753                     _result=_engine.wrap(_gather[0],_outBuffer);
754                     _outNIOBuffer.setGetIndex(0);
755                     _outNIOBuffer.setPutIndex(_result.bytesProduced());
756                     consumed=_result.bytesConsumed();
757                 }
758                 finally
759                 {
760                     _outBuffer.position(0);
761 
762                     if (consumed>0 && buffer!=null)
763                     {
764                         int len=consumed<buffer.length()?consumed:buffer.length();
765                         buffer.skip(len);
766                         consumed-=len;
767                         _gather[0].position(0);
768                         _gather[0].limit(_gather[0].capacity());
769                     }
770                     assert consumed==0;
771                 }
772             }
773         }
774         switch(_result.getStatus())
775         {
776             case BUFFER_OVERFLOW:
777             case BUFFER_UNDERFLOW:
778                 Log.warn("wrap {}",_result);
779                 
780             case OK:
781                 return _result.bytesConsumed();
782             case CLOSED:
783                 _closing=true;
784                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
785 
786             default:
787                 Log.warn("wrap "+_result);
788             throw new IOException(_result.toString());
789         }
790     }
791 
792     /* ------------------------------------------------------------ */
793     public boolean isBufferingInput()
794     {
795         return _inNIOBuffer.hasContent();
796     }
797 
798     /* ------------------------------------------------------------ */
799     public boolean isBufferingOutput()
800     {
801         return _outNIOBuffer.hasContent();
802     }
803 
804     /* ------------------------------------------------------------ */
805     public boolean isBufferred()
806     {
807         return true;
808     }
809 
810     /* ------------------------------------------------------------ */
811     public SSLEngine getSSLEngine()
812     {
813         return _engine;
814     }
815 
816     /* ------------------------------------------------------------ */
817     public String toString()
818     {
819         return super.toString()+","+_engine.getHandshakeStatus()+", in/out="+_inNIOBuffer.length()+"/"+_outNIOBuffer.length()+" "+_result;
820     }
821 }