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 }