View Javadoc

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.io.nio;
15  
16  import java.io.IOException;
17  import java.nio.ByteBuffer;
18  import java.nio.channels.SelectionKey;
19  import java.nio.channels.SocketChannel;
20  import javax.net.ssl.SSLEngine;
21  import javax.net.ssl.SSLEngineResult;
22  import javax.net.ssl.SSLEngineResult.HandshakeStatus;
23  import javax.net.ssl.SSLException;
24  import javax.net.ssl.SSLSession;
25  
26  import org.eclipse.jetty.io.Buffer;
27  import org.eclipse.jetty.io.Buffers;
28  import org.eclipse.jetty.io.EofException;
29  import org.eclipse.jetty.util.log.Log;
30  import org.eclipse.jetty.util.log.Logger;
31  
32  /* ------------------------------------------------------------ */
33  /**
34   * SslSelectChannelEndPoint
35   * <p>
36   * A SelectChannelEndPoint that uses an {@link SSLEngine} to handle an
37   * SSL connection.
38   * <p>
39   * There is a named logger "org.eclipse.jetty.http.ssl"
40   * </p>
41   */
42  public class SslSelectChannelEndPoint extends SelectChannelEndPoint
43  {
44      public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio").getLogger("ssl");
45  
46      private static final Buffer __EMPTY_BUFFER=new DirectNIOBuffer(0);
47      private static final ByteBuffer __ZERO_BUFFER=ByteBuffer.allocate(0);
48  
49      private final Buffers _buffers;
50  
51      private final SSLEngine _engine;
52      private final SSLSession _session;
53      private volatile NIOBuffer _inNIOBuffer;
54      private volatile NIOBuffer _outNIOBuffer;
55  
56      private boolean _closing=false;
57      private SSLEngineResult _result;
58  
59      private volatile boolean _handshook=false;
60      private boolean _allowRenegotiate=true;
61  
62      private volatile boolean _debug = LOG.isDebugEnabled(); // snapshot debug status for optimizer
63  
64      /* ------------------------------------------------------------ */
65      public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine, int maxIdleTime)
66              throws IOException
67      {
68          super(channel,selectSet,key, maxIdleTime);
69          _buffers=buffers;
70  
71          // ssl
72          _engine=engine;
73          _session=engine.getSession();
74  
75          if (_debug) LOG.debug(_session+" channel="+channel);
76      }
77  
78      /* ------------------------------------------------------------ */
79      public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine)
80              throws IOException
81      {
82          super(channel,selectSet,key);
83          _buffers=buffers;
84  
85          // ssl
86          _engine=engine;
87          _session=engine.getSession();
88  
89          if (_debug) LOG.debug(_session+" channel="+channel);
90      }
91  
92  
93      /* ------------------------------------------------------------ */
94      private void needOutBuffer()
95      {
96          synchronized (this)
97          {
98              if (_outNIOBuffer==null)
99                  _outNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize());
100         }
101     }
102 
103     /* ------------------------------------------------------------ */
104     private void freeOutBuffer()
105     {
106         synchronized (this)
107         {
108             if (_outNIOBuffer!=null && _outNIOBuffer.length()==0)
109             {
110                 _buffers.returnBuffer(_outNIOBuffer);
111                 _outNIOBuffer=null;
112             }
113         }
114     }
115 
116     /* ------------------------------------------------------------ */
117     private void needInBuffer()
118     {
119         synchronized (this)
120         {
121             if(_inNIOBuffer==null)
122                 _inNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize());
123         }
124     }
125 
126     /* ------------------------------------------------------------ */
127     private void freeInBuffer()
128     {
129         synchronized (this)
130         {
131             if (_inNIOBuffer!=null && _inNIOBuffer.length()==0)
132             {
133                 _buffers.returnBuffer(_inNIOBuffer);
134                 _inNIOBuffer=null;
135             }
136         }
137     }
138 
139     /* ------------------------------------------------------------ */
140     /**
141      * @return True if the endpoint has produced/consumed bytes itself (non application data).
142      */
143     public boolean isProgressing()
144     {
145         SSLEngineResult result = _result;
146         _result=null;
147         return result!=null && (result.bytesConsumed()>0 || result.bytesProduced()>0);
148     }
149 
150     /* ------------------------------------------------------------ */
151     /**
152      * @return True if SSL re-negotiation is allowed (default false)
153      */
154     public boolean isAllowRenegotiate()
155     {
156         return _allowRenegotiate;
157     }
158 
159     /* ------------------------------------------------------------ */
160     /**
161      * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
162      * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
163      * does not have CVE-2009-3555 fixed, then re-negotiation should
164      * not be allowed.
165      * @param allowRenegotiate true if re-negotiation is allowed (default false)
166      */
167     public void setAllowRenegotiate(boolean allowRenegotiate)
168     {
169         _allowRenegotiate = allowRenegotiate;
170     }
171 
172 
173     /* ------------------------------------------------------------ */
174     @Override
175     public boolean isOutputShutdown()
176     {
177         return _engine!=null && _engine.isOutboundDone();
178     }
179 
180     /* ------------------------------------------------------------ */
181     @Override
182     public boolean isInputShutdown()
183     {
184         return _engine!=null && _engine.isInboundDone();
185     }
186 
187     /* ------------------------------------------------------------ */
188     @Override
189     public void shutdownOutput() throws IOException
190     {
191         LOG.debug("{} shutdownOutput",_session);
192         // All SSL closes should be graceful, as it is more secure.
193         // So normal SSL close can be used here.
194         close();
195     }
196 
197     /* ------------------------------------------------------------ */
198     private int process(ByteBuffer inBBuf, Buffer outBuf) throws IOException
199     {
200         if (_debug)
201             LOG.debug("{} process closing={} in={} out={}",_session,_closing,inBBuf,outBuf);
202 
203         // If there is no place to put incoming application data,
204         if (inBBuf==null)
205         {
206             // use ZERO buffer
207             inBBuf=__ZERO_BUFFER;
208         }
209 
210         int received=0;
211         int sent=0;
212 
213 
214         HandshakeStatus initialStatus = _engine.getHandshakeStatus();
215         boolean progress=true;
216 
217         while (progress)
218         {
219             progress=false;
220 
221             // flush output data
222             int len=_outNIOBuffer==null?0:_outNIOBuffer.length();
223 
224             // we must flush it, as the other end might be
225             // waiting for that outgoing data before sending
226             // more incoming data
227             flush();
228 
229             // If we have written some bytes, then progress has been made.
230             progress|=(_outNIOBuffer==null?0:_outNIOBuffer.length())<len;
231 
232             // handle the current hand share status
233             if (_debug) LOG.debug("status {} {}",_engine,_engine.getHandshakeStatus());
234             switch(_engine.getHandshakeStatus())
235             {
236                 case FINISHED:
237                     throw new IllegalStateException();
238 
239                 case NOT_HANDSHAKING:
240 
241                     // If closing, don't process application data
242                     if (_closing)
243                     {
244                         if (outBuf!=null && outBuf.hasContent())
245                             throw new IOException("Write while closing");
246                         break;
247                     }
248 
249                     // Try wrapping some application data
250                     if (outBuf!=null && outBuf.hasContent())
251                     {
252                         int c=wrap(outBuf);
253                         progress=c>0||_result.bytesProduced()>0||_result.bytesConsumed()>0;
254 
255                         if (c>0)
256                             sent+=c;
257                         else if (c<0 && sent==0)
258                             sent=-1;
259                     }
260 
261                     // Try unwrapping some application data
262                     if (inBBuf.remaining()>0 && _inNIOBuffer!=null && _inNIOBuffer.hasContent())
263                     {
264                         int space=inBBuf.remaining();
265                         progress|=unwrap(inBBuf);
266                         received+=space-inBBuf.remaining();
267                     }
268                     break;
269 
270 
271                 case NEED_TASK:
272                 {
273                     // A task needs to be run, so run it!
274                     Runnable task;
275                     while ((task=_engine.getDelegatedTask())!=null)
276                     {
277                         progress=true;
278                         task.run();
279                     }
280 
281                     // Detect SUN JVM Bug!!!
282                     if(initialStatus==HandshakeStatus.NOT_HANDSHAKING &&
283                        _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && sent==0)
284                     {
285                         // This should be NEED_WRAP
286                         // 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.
287                         // 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.
288                         // See http://jira.codehaus.org/browse/JETTY-567 for more details
289                         if (_debug) LOG.warn("{} JETTY-567",_session);
290                         return -1;
291                     }
292                     break;
293                 }
294 
295                 case NEED_WRAP:
296                 {
297                     checkRenegotiate();
298 
299                     // The SSL needs to send some handshake data to the other side
300                     int c=0;
301                     if (outBuf!=null && outBuf.hasContent())
302                         c=wrap(outBuf);
303                     else
304                         c=wrap(__EMPTY_BUFFER);
305 
306                     progress=_result.bytesProduced()>0||_result.bytesConsumed()>0;
307                     if (c>0)
308                         sent+=c;
309                     else if (c<0 && sent==0)
310                         sent=-1;
311                     break;
312                 }
313 
314                 case NEED_UNWRAP:
315                 {
316                     checkRenegotiate();
317 
318                     // Need more data to be unwrapped so try another call to unwrap
319                     progress|=unwrap(inBBuf);
320                     if (_closing)
321                         inBBuf.clear();
322                     break;
323                 }
324             }
325 
326             if (_debug) LOG.debug("{} progress {}",_session,progress);
327         }
328 
329         if (_debug) LOG.debug("{} received {} sent {}",_session,received,sent);
330 
331         freeInBuffer();
332         return (received<0||sent<0)?-1:(received+sent);
333     }
334 
335 
336 
337     /* ------------------------------------------------------------ */
338     @Override
339     public void close() throws IOException
340     {
341         if (_closing)
342             return;
343 
344         _closing=true;
345         LOG.debug("{} close",_session);
346         try
347         {
348             _engine.closeOutbound();
349             process(null,null);
350         }
351         catch (IOException e)
352         {
353             // We could not write the SSL close message because the
354             // socket was already closed, nothing more we can do.
355             LOG.ignore(e);
356         }
357         finally
358         {
359             super.close();
360         }
361     }
362 
363     /* ------------------------------------------------------------ */
364     /** Fill the buffer with unencrypted bytes.
365      * Called by a Http Parser when more data is
366      * needed to continue parsing a request or a response.
367      */
368     @Override
369     public int fill(Buffer buffer) throws IOException
370     {
371         _debug=LOG.isDebugEnabled();
372         LOG.debug("{} fill",_session);
373         // This end point only works on NIO buffer type (director
374         // or indirect), so extract the NIO buffer that is wrapped
375         // by the passed jetty Buffer.
376         ByteBuffer bbuf=((NIOBuffer)buffer).getByteBuffer();
377 
378 
379         // remember the original size of the unencrypted buffer
380         int size=buffer.length();
381 
382 
383         synchronized (bbuf)
384         {
385             bbuf.position(buffer.putIndex());
386             try
387             {
388                 // Call the SSLEngine unwrap method to process data in
389                 // the inBuffer.  If there is no data in the inBuffer, then
390                 // super.fill is called to read encrypted bytes.
391                 unwrap(bbuf);
392                 process(bbuf,null);
393             }
394             finally
395             {
396                 // reset the Buffers
397                 buffer.setPutIndex(bbuf.position());
398                 bbuf.position(0);
399             }
400         }
401         // return the number of unencrypted bytes filled.
402         int filled=buffer.length()-size;
403         if (filled==0 && (isInputShutdown() || !isOpen()))
404             return -1;
405 
406         return filled;
407     }
408 
409     /* ------------------------------------------------------------ */
410     @Override
411     public int flush(Buffer buffer) throws IOException
412     {
413         _debug=LOG.isDebugEnabled();
414         LOG.debug("{} flush1",_session);
415         return process(null,buffer);
416     }
417 
418 
419     /* ------------------------------------------------------------ */
420     /*
421      */
422     @Override
423     public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
424     {
425         _debug=LOG.isDebugEnabled();
426         LOG.debug("{} flush3",_session);
427 
428         int len=0;
429         int flushed=0;
430         if (header!=null && header.hasContent())
431         {
432             len=header.length();
433             flushed=flush(header);
434         }
435         if (flushed==len && buffer!=null && buffer.hasContent())
436         {
437             int f=flush(buffer);
438             if (f>=0)
439                 flushed+=f;
440             else if (flushed==0)
441                 flushed=-1;
442         }
443 
444         return flushed;
445     }
446 
447     /* ------------------------------------------------------------ */
448     @Override
449     public void flush() throws IOException
450     {
451         LOG.debug("{} flush",_session);
452         if (!isOpen())
453             throw new EofException();
454 
455         if (isBufferingOutput())
456         {
457             int flushed=super.flush(_outNIOBuffer);
458             if (_debug)
459                 LOG.debug("{} flushed={} left={}",_session,flushed,_outNIOBuffer.length());
460         }
461         else if (_engine.isOutboundDone() && super.isOpen())
462         {
463             if (_debug)
464                 LOG.debug("{} flush shutdownOutput",_session);
465             try
466             {
467                 super.shutdownOutput();
468             }
469             catch(IOException e)
470             {
471                 LOG.ignore(e);
472             }
473         }
474 
475         freeOutBuffer();
476     }
477 
478     /* ------------------------------------------------------------ */
479     private void checkRenegotiate() throws IOException
480     {
481         if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen())
482         {
483             LOG.warn("SSL renegotiate denied: {}",_channel);
484             super.close();
485         }
486     }
487 
488     /* ------------------------------------------------------------ */
489     /**
490      * @return true if progress is made
491      */
492     private boolean unwrap(ByteBuffer buffer) throws IOException
493     {
494         needInBuffer();
495         ByteBuffer in_buffer=_inNIOBuffer.getByteBuffer();
496 
497         _inNIOBuffer.compact();
498 
499         int total_filled=0;
500         boolean remoteClosed = false;
501 
502         LOG.debug("{} unwrap space={} open={}",_session,_inNIOBuffer.space(),super.isOpen());
503 
504         // loop filling as much encrypted data as we can into the buffer
505         while (_inNIOBuffer.space()>0 && super.isOpen())
506         {
507             int filled=super.fill(_inNIOBuffer);
508             if (_debug) LOG.debug("{} filled {}",_session,filled);
509             if (filled < 0)
510                 remoteClosed = true;
511             // break the loop if no progress is made (we have read everything there is to read)
512             if (filled<=0)
513                 break;
514             total_filled+=filled;
515         }
516 
517         // If we have no progress and no data
518         if (total_filled==0 && _inNIOBuffer.length()==0)
519         {
520             // Do we need to close?
521             if (isOpen() && remoteClosed)
522             {
523                 try
524                 {
525                     _engine.closeInbound();
526                 }
527                 catch (SSLException x)
528                 {
529                     // It may happen, for example, in case of truncation
530                     // attacks, we close so that we do not spin forever
531                     super.close();
532                 }
533             }
534 
535             if (!isOpen())
536                 throw new EofException();
537 
538             return false;
539         }
540 
541         // We have some in data, so try to unwrap it.
542         try
543         {
544             // inBuffer is the NIO buffer inside the _inNIOBuffer,
545             // so update its position and limit from the inNIOBuffer.
546             in_buffer.position(_inNIOBuffer.getIndex());
547             in_buffer.limit(_inNIOBuffer.putIndex());
548 
549             // Do the unwrap
550             _result=_engine.unwrap(in_buffer,buffer);
551             if (!_handshook && _result.getHandshakeStatus()==SSLEngineResult.HandshakeStatus.FINISHED)
552                 _handshook=true;
553             if (_debug) LOG.debug("{} unwrap {}",_session,_result);
554 
555             // skip the bytes consumed
556             _inNIOBuffer.skip(_result.bytesConsumed());
557         }
558         catch(SSLException e)
559         {
560             LOG.warn(getRemoteAddr() + ":" + getRemotePort() + " ",e);
561             super.close();
562             throw e;
563         }
564         finally
565         {
566             // reset the buffer so it can be managed by the _inNIOBuffer again.
567             in_buffer.position(0);
568             in_buffer.limit(in_buffer.capacity());
569         }
570 
571         // handle the unwrap results
572         switch(_result.getStatus())
573         {
574             case BUFFER_OVERFLOW:
575                 LOG.debug("{} unwrap overflow",_session);
576                 return false;
577 
578             case BUFFER_UNDERFLOW:
579                 // Not enough data,
580                 // If we are closed, we will never get more, so EOF
581                 // else return and we will be tried again
582                 // later when more data arriving causes another dispatch.
583                 if (LOG.isDebugEnabled()) LOG.debug("{} unwrap {}",_session,_result);
584                 if(!isOpen())
585                 {
586                     _inNIOBuffer.clear();
587                     if (_outNIOBuffer!=null)
588                         _outNIOBuffer.clear();
589                     throw new EofException();
590                 }
591                 return (total_filled > 0);
592 
593             case CLOSED:
594                 _closing=true;
595                 // return true is some bytes somewhere were moved about.
596                 return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0;
597 
598             case OK:
599                 // return true is some bytes somewhere were moved about.
600                 return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0;
601 
602             default:
603                 LOG.warn("{} unwrap default: {}",_session,_result);
604                 throw new IOException(_result.toString());
605         }
606     }
607 
608     /* ------------------------------------------------------------ */
609     private ByteBuffer extractOutputBuffer(Buffer buffer)
610     {
611         if (buffer.buffer() instanceof NIOBuffer)
612             return ((NIOBuffer)buffer.buffer()).getByteBuffer();
613 
614         return ByteBuffer.wrap(buffer.array());
615     }
616 
617     /* ------------------------------------------------------------ */
618     private int wrap(final Buffer buffer) throws IOException
619     {
620         ByteBuffer bbuf=extractOutputBuffer(buffer);
621         synchronized(bbuf)
622         {
623             int consumed=0;
624             needOutBuffer();
625             _outNIOBuffer.compact();
626             ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer();
627             synchronized(out_buffer)
628             {
629                 try
630                 {
631                     bbuf.position(buffer.getIndex());
632                     bbuf.limit(buffer.putIndex());
633                     out_buffer.position(_outNIOBuffer.putIndex());
634                     out_buffer.limit(out_buffer.capacity());
635                     _result=_engine.wrap(bbuf,out_buffer);
636                     if (_debug) LOG.debug("{} wrap {}",_session,_result);
637                     if (!_handshook && _result.getHandshakeStatus()==SSLEngineResult.HandshakeStatus.FINISHED)
638                         _handshook=true;
639                     _outNIOBuffer.setPutIndex(out_buffer.position());
640                     consumed=_result.bytesConsumed();
641                 }
642                 catch(SSLException e)
643                 {
644                     LOG.warn(getRemoteAddr()+":"+getRemotePort()+" ",e);
645                     super.close();
646                     throw e;
647                 }
648                 finally
649                 {
650                     out_buffer.position(0);
651                     bbuf.position(0);
652                     bbuf.limit(bbuf.capacity());
653 
654                     if (consumed>0)
655                     {
656                         int len=consumed<buffer.length()?consumed:buffer.length();
657                         buffer.skip(len);
658                         consumed-=len;
659                     }
660                 }
661             }
662         }
663         switch(_result.getStatus())
664         {
665             case BUFFER_UNDERFLOW:
666                 throw new IllegalStateException();
667 
668             case BUFFER_OVERFLOW:
669                 LOG.debug("{} wrap {}",_session,_result);
670                 flush();
671                 return 0;
672 
673             case OK:
674                 return _result.bytesConsumed();
675             case CLOSED:
676                 _closing=true;
677                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
678 
679             default:
680                 LOG.warn("{} wrap default {}",_session,_result);
681             throw new IOException(_result.toString());
682         }
683     }
684 
685     /* ------------------------------------------------------------ */
686     @Override
687     public boolean isBufferingInput()
688     {
689         final Buffer in = _inNIOBuffer;
690         return in!=null && in.hasContent();
691     }
692 
693     /* ------------------------------------------------------------ */
694     @Override
695     public boolean isBufferingOutput()
696     {
697         final NIOBuffer out = _outNIOBuffer;
698         return out!=null && out.hasContent();
699     }
700 
701     /* ------------------------------------------------------------ */
702     @Override
703     public boolean isBufferred()
704     {
705         return true;
706     }
707 
708     /* ------------------------------------------------------------ */
709     public SSLEngine getSSLEngine()
710     {
711         return _engine;
712     }
713 
714     /* ------------------------------------------------------------ */
715     @Override
716     public String toString()
717     {
718         final NIOBuffer i=_inNIOBuffer;
719         final NIOBuffer o=_outNIOBuffer;
720         return "SSL"+super.toString()+","+(_engine==null?"-":_engine.getHandshakeStatus())+", in/out="+
721         (i==null?0:i.length())+"/"+(o==null?0:o.length())+
722         " bi/o="+isBufferingInput()+"/"+isBufferingOutput()+
723         " "+_result;
724     }
725 }