View Javadoc

1   /*
2    *   @(#) $Id: SSLHandler.java 169321 2005-05-09 15:00:25Z trustin $
3    *
4    *   Copyright 2004 The Apache Software Foundation
5    *
6    *   Licensed under the Apache License, Version 2.0 (the "License");
7    *   you may not use this file except in compliance with the License.
8    *   You may obtain a copy of the License at
9    *
10   *       http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing, software
13   *   distributed under the License is distributed on an "AS IS" BASIS,
14   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *   See the License for the specific language governing permissions and
16   *   limitations under the License.
17   *
18   */
19  package org.apache.mina.io.filter;
20  
21  import java.nio.ByteBuffer;
22  import java.util.logging.Level;
23  import java.util.logging.Logger;
24  
25  import javax.net.ssl.SSLContext;
26  import javax.net.ssl.SSLEngine;
27  import javax.net.ssl.SSLEngineResult;
28  import javax.net.ssl.SSLException;
29  import javax.net.ssl.SSLSession;
30  
31  import org.apache.mina.io.IoSession;
32  import org.apache.mina.io.IoFilter.NextFilter;
33  import org.apache.mina.util.Queue;
34  
35  /***
36   * A helper class using the SSLEngine API to decrypt/encrypt data.
37   * <p>
38   * Each connection has a SSLEngine that is used through the lifetime of the connection.
39   * We allocate byte buffers for use as the outbound and inbound network buffers.
40   * These buffers handle all of the intermediary data for the SSL connection. To make things easy,
41   * we'll require outNetBuffer be completely flushed before trying to wrap any more data.
42   *
43   * @author Jan Andersson (janne@minq.se)
44   * @version $Rev: 169321 $, $Date: 2005-05-10 00:00:25 +0900 (?, 10  5? 2005) $
45   */
46  class SSLHandler
47  {
48      private static final Logger log = Logger.getLogger( SSLFilter.class.getName() );
49  
50      private final SSLFilter parent;
51  
52      private final IoSession session;
53      
54      private final Queue nextFilterQueue = new Queue();
55      
56      private final Queue writeBufferQueue = new Queue();
57      
58      private final Queue writeMarkerQueue = new Queue();
59  
60      private SSLEngine sslEngine;
61  
62      /***
63       * Encrypted data from the net
64       */
65      private ByteBuffer inNetBuffer;
66  
67      /***
68       * Encrypted data to be written to the net
69       */
70      private ByteBuffer outNetBuffer;
71  
72      /***
73       * Applicaton cleartext data to be read by application
74       */
75      private ByteBuffer appBuffer;
76  
77      /***
78       * Empty buffer used during initial handshake and close operations
79       */
80      private ByteBuffer hsBB = ByteBuffer.allocate( 0 );
81  
82      /***
83       * Handshake status
84       */
85      private SSLEngineResult.HandshakeStatus initialHandshakeStatus;
86  
87      /***
88       * Initial handshake complete?
89       */
90      private boolean initialHandshakeComplete;
91  
92      /***
93       * We have received the shutdown request by our caller, and have
94       * closed our outbound side.
95       */
96      private boolean shutdown = false;
97  
98      private boolean closed = false;
99  
100     private boolean isWritingEncryptedData = false;
101     
102     /***
103      * Constuctor.
104      *
105      * @param sslc
106      * @throws SSLException 
107      */
108     SSLHandler( SSLFilter parent, SSLContext sslc, IoSession session ) throws SSLException
109     {
110         this.parent = parent;
111         this.session = session;
112         sslEngine = sslc.createSSLEngine();
113         sslEngine.setUseClientMode( parent.isUseClientMode() );
114         sslEngine.setNeedClientAuth( parent.isNeedClientAuth() );
115         sslEngine.setWantClientAuth( parent.isWantClientAuth() );
116   
117         if( parent.getEnabledCipherSuites() != null )
118         {
119             sslEngine.setEnabledCipherSuites( parent.getEnabledCipherSuites() );
120         }
121         
122         if( parent.getEnabledProtocols() != null )
123         {
124             sslEngine.setEnabledProtocols( parent.getEnabledProtocols() );
125         }
126 
127         sslEngine.beginHandshake();   
128         initialHandshakeStatus = sslEngine.getHandshakeStatus();//SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
129         initialHandshakeComplete = false;
130         //SSLSession sslSession = sslEngine.getSession
131         
132         SSLByteBufferPool.initiate( sslEngine );
133 
134         appBuffer = SSLByteBufferPool.getApplicationBuffer();
135 
136         inNetBuffer = SSLByteBufferPool.getPacketBuffer();
137         outNetBuffer = SSLByteBufferPool.getPacketBuffer();
138         outNetBuffer.position( 0 );
139         outNetBuffer.limit( 0 );
140     }
141 
142     /***
143      * Indicate that we are writing encrypted data.
144      * Only used as a flag by IoSSLFiler
145      */
146     public void setWritingEncryptedData( boolean flag )
147     {
148         isWritingEncryptedData = flag;
149     }
150 
151     /***
152      * Check we are writing encrypted data.
153      */
154     public boolean isWritingEncryptedData()
155     {
156         return isWritingEncryptedData;
157     }
158 
159     /***
160      * Check if initial handshake is completed.
161      */
162     public boolean isInitialHandshakeComplete()
163     {
164         return initialHandshakeComplete;
165     }
166 
167     /***
168      * Check if SSL sesssion closed
169      */
170     public boolean isClosed()
171     {
172         return closed;
173     }
174 
175     /***
176      * Check if there is any need to complete initial handshake.
177      */
178     public boolean needToCompleteInitialHandshake()
179     {
180         return ( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP && !closed );
181     }
182     
183     public synchronized void scheduleWrite( NextFilter nextFilter, org.apache.mina.common.ByteBuffer buf, Object marker )
184     {
185         nextFilterQueue.push( nextFilter );
186         writeBufferQueue.push( buf );
187         writeMarkerQueue.push( marker );
188     }
189     
190     public synchronized void flushScheduledWrites() throws SSLException
191     {
192         NextFilter nextFilter;
193         org.apache.mina.common.ByteBuffer scheduledBuf;
194         Object scheduledMarker;
195         
196         while( ( scheduledBuf = ( org.apache.mina.common.ByteBuffer ) writeBufferQueue.pop() ) != null )
197         {
198             if( log.isLoggable( Level.FINEST ) )
199             {
200                 log.log( Level.FINEST, session + " Flushing buffered write request: " + scheduledBuf );
201             }
202             nextFilter = ( NextFilter ) nextFilterQueue.pop();
203             scheduledMarker = writeMarkerQueue.pop();
204             parent.filterWrite( nextFilter, session, scheduledBuf, scheduledMarker );
205         }
206     }
207 
208     /***
209      * Call when data read from net. Will perform inial hanshake or decrypt provided
210      * Buffer.
211      * Decrytpted data reurned by getAppBuffer(), if any.
212      *
213      * @param buf buffer to decrypt
214      * @throws SSLException on errors
215      */
216     public void dataRead( NextFilter nextFilter, ByteBuffer buf ) throws SSLException
217     {
218         if ( buf.limit() > inNetBuffer.remaining() ) {
219             // We have to expand inNetBuffer
220             inNetBuffer = SSLByteBufferPool.expandBuffer( inNetBuffer,
221                 inNetBuffer.capacity() + ( buf.limit() * 2 ) );
222             // We also expand app. buffer (twice the size of in net. buffer)
223             appBuffer = SSLByteBufferPool.expandBuffer( appBuffer, inNetBuffer.capacity() * 2);
224             appBuffer.position( 0 );
225             appBuffer.limit( 0 );
226             if( log.isLoggable( Level.FINEST ) )
227             {
228                 log.log( Level.FINEST, session + 
229                                     " expanded inNetBuffer:" + inNetBuffer );
230                 log.log( Level.FINEST, session + 
231                                     " expanded appBuffer:" + appBuffer );
232             }
233         }
234 
235         // append buf to inNetBuffer
236         inNetBuffer.put( buf );
237         if( !initialHandshakeComplete )
238         {
239             doHandshake( nextFilter );
240         }
241         else
242         {
243             doDecrypt();
244         }
245     }
246 
247     /***
248      * Continue initial SSL handshake.
249      *
250      * @throws SSLException on errors
251      */
252     public void continueHandshake( NextFilter nextFilter ) throws SSLException
253     {
254         if( log.isLoggable( Level.FINEST ) )
255         {
256             log.log( Level.FINEST, session + " continueHandshake()" );
257         }
258         doHandshake( nextFilter );
259     }
260 
261     /***
262      * Get decrypted application data.
263      *
264      * @return buffer with data
265      */
266     public ByteBuffer getAppBuffer()
267     {
268         return appBuffer;
269     }
270 
271     /***
272      * Get encrypted data to be sent.
273      *
274      * @return buffer with data
275      */
276     public ByteBuffer getOutNetBuffer()
277     {
278         return outNetBuffer;
279     }
280 
281     /***
282      * Encrypt provided buffer. Encytpted data reurned by getOutNetBuffer().
283      *
284      * @param buf data to encrypt
285      * @throws SSLException on errors
286      */
287     public void encrypt( ByteBuffer buf ) throws SSLException
288     {
289         doEncrypt( buf );
290     }
291 
292     /***
293      * Start SSL shutdown process
294      *
295      * @throws SSLException on errors
296      */
297     public void shutdown() throws SSLException
298     {
299         if( !shutdown )
300         {
301             doShutdown();
302         }
303     }
304 
305     /***
306      * Release allocated ByteBuffers.
307      */
308     public void release()
309     {
310         SSLByteBufferPool.release( appBuffer );
311         SSLByteBufferPool.release( inNetBuffer );
312         SSLByteBufferPool.release( outNetBuffer );
313     }
314 
315     /***
316      * Decrypt in net buffer. Result is stored in app buffer.
317      *
318      * @throws SSLException
319      */
320     private void doDecrypt() throws SSLException
321     {
322 
323         if( !initialHandshakeComplete )
324         {
325             throw new IllegalStateException();
326         }
327 
328         if( appBuffer.hasRemaining() )
329         {
330              if ( log.isLoggable( Level.FINEST ) ) {
331                  log.log( Level.FINEST, session + " Error: appBuffer not empty!" );
332              }
333             //still app data in buffer!?
334             throw new IllegalStateException();
335         }
336 
337         unwrap();
338     }
339 
340     /***
341      * @param status
342      * @throws SSLException
343      */
344     private SSLEngineResult.Status checkStatus( SSLEngineResult.Status status ) throws SSLException
345     {
346         if( status != SSLEngineResult.Status.OK &&
347             status != SSLEngineResult.Status.CLOSED &&
348             status != SSLEngineResult.Status.BUFFER_UNDERFLOW )
349         {
350             throw new SSLException( "SSLEngine error during decrypt: " +
351                                     status +
352                                     " inNetBuffer: " + inNetBuffer + "appBuffer: " + appBuffer);
353         }
354         
355         return status;
356     }
357     
358     private void doEncrypt( ByteBuffer src ) throws SSLException
359     {
360         if( !initialHandshakeComplete )
361         {
362             throw new IllegalStateException();
363         }
364 
365         // The data buffer is (must be) empty, we can reuse the entire
366         // buffer.
367         outNetBuffer.clear();
368 
369         SSLEngineResult result;
370 
371         // Loop until there is no more data in src
372         while ( src.hasRemaining() ) {
373 
374             if ( src.remaining() > ( ( outNetBuffer.capacity() - outNetBuffer.position() ) / 2 ) ) {
375                 // We have to expand outNetBuffer
376                 // Note: there is no way to know the exact size required, but enrypted data
377                 // shouln't need to be larger than twice the source data size?
378                 outNetBuffer = SSLByteBufferPool.expandBuffer( outNetBuffer, src.capacity() * 2 );
379                 if ( log.isLoggable( Level.FINEST ) ) {
380                     log.log( Level.FINEST, session + " expanded outNetBuffer:" + outNetBuffer );
381                 }
382             }
383 
384             result = sslEngine.wrap( src, outNetBuffer );
385             if ( log.isLoggable( Level.FINEST ) ) {
386                 log.log( Level.FINEST, session + " Wrap res:" + result );
387             }
388 
389             if ( result.getStatus() == SSLEngineResult.Status.OK ) {
390                 if ( result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK ) {
391                     doTasks();
392                 }
393             } else {
394                 throw new SSLException( "SSLEngine error during encrypt: "
395                         + result.getStatus() +
396                         " src: " + src + "outNetBuffer: " + outNetBuffer);
397             }
398         }
399 
400         outNetBuffer.flip();
401     }
402 
403     /***
404      * Perform any handshaking processing.
405      */
406     synchronized void doHandshake( NextFilter nextFilter ) throws SSLException
407     {
408 
409         if( log.isLoggable( Level.FINEST ) )
410         {
411             log.log( Level.FINEST, session + " doHandshake()" );
412         }
413 
414         while( !initialHandshakeComplete )
415         {
416             if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED )
417             {
418                 if( log.isLoggable( Level.FINEST ) )
419                 {
420                     SSLSession sslSession = sslEngine.getSession();
421                     log.log( Level.FINEST, session + "  initialHandshakeStatus=FINISHED" );
422                     log.log( Level.FINEST, session + "  sslSession CipherSuite used " + sslSession.getCipherSuite());
423                 }
424                 initialHandshakeComplete = true;
425                 return;
426             }
427             else if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK )
428             {
429                 if( log.isLoggable( Level.FINEST ) )
430                 {
431                     log.log( Level.FINEST, session + "  initialHandshakeStatus=NEED_TASK" );
432                 }
433                 initialHandshakeStatus = doTasks();
434             }
435             else if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP )
436             {
437                 // we need more data read
438                 if( log.isLoggable( Level.FINEST ) )
439                 {
440                     log.log( Level.FINEST, session +
441                              "  initialHandshakeStatus=NEED_UNWRAP" );
442                 }
443                 SSLEngineResult.Status status = unwrapHandshake();
444                 if( ( initialHandshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED 
445                 		&&  status == SSLEngineResult.Status.BUFFER_UNDERFLOW )
446                         || closed )
447                 {
448                     // We need more data or the session is closed
449                     return;
450                 }
451             }
452             else if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP )
453             {
454                 if( log.isLoggable( Level.FINEST ) )
455                 {
456                     log.log( Level.FINEST, session + "  initialHandshakeStatus=NEED_WRAP" );
457                 }
458                 // First make sure that the out buffer is completely empty. Since we
459                 // cannot call wrap with data left on the buffer
460                 if( outNetBuffer.hasRemaining() )
461                 {
462                     if( log.isLoggable( Level.FINEST ) )
463                     {
464                         log.log( Level.FINEST, session + "  Still data in out buffer!" );
465                     }
466                     return;
467                 }
468                 outNetBuffer.clear();
469                 SSLEngineResult result = sslEngine.wrap( hsBB, outNetBuffer );
470                 if( log.isLoggable( Level.FINEST ) )
471                 {
472                     log.log( Level.FINEST, session + " Wrap res:" + result );
473                 }
474 
475                 outNetBuffer.flip();
476                 initialHandshakeStatus = result.getHandshakeStatus();
477                 parent.writeNetBuffer( nextFilter, session, this );
478                 // return to allow data on out buffer being sent
479                 // TODO: We might want to send more data immidiatley?
480             }
481             else
482             {
483                 throw new IllegalStateException( "Invalid Handshaking State"
484                         + initialHandshakeStatus );
485             }
486         }
487     }
488 
489     SSLEngineResult.Status unwrap() throws SSLException
490     {
491         if( log.isLoggable( Level.FINEST ) )
492         {
493             log.log( Level.FINEST, session + " unwrap()" );
494         }
495         // Prepare the application buffer to receive decrypted data
496         appBuffer.clear();
497 
498         // Prepare the net data for reading.
499         inNetBuffer.flip();
500 
501         SSLEngineResult res;
502         do
503         {
504             if( log.isLoggable( Level.FINEST ) )
505             {
506                 log.log( Level.FINEST, session + "   inNetBuffer: " + inNetBuffer );
507                 log.log( Level.FINEST, session + "   appBuffer: " + appBuffer );
508             }
509             res = sslEngine.unwrap( inNetBuffer, appBuffer );
510             if( log.isLoggable( Level.FINEST ) )
511             {
512                 log.log( Level.FINEST, session + " Unwrap res:" + res );
513             }
514         }
515         while( res.getStatus() == SSLEngineResult.Status.OK );
516 
517         // If we are CLOSED, set flag
518         if( res.getStatus() == SSLEngineResult.Status.CLOSED )
519         {
520             closed = true;
521         }
522 
523         // prepare to be written again
524         inNetBuffer.compact();
525         // prepare app data to be read
526         appBuffer.flip();
527 
528         /*
529          * The status may be:
530          * OK - Normal operation
531          * OVERFLOW - Should never happen since the application buffer is
532          *      sized to hold the maximum packet size.
533          * UNDERFLOW - Need to read more data from the socket. It's normal.
534          * CLOSED - The other peer closed the socket. Also normal.
535          */
536         return checkStatus( res.getStatus() );
537     }
538 
539     private SSLEngineResult.Status unwrapHandshake() throws SSLException
540     {
541         if( log.isLoggable( Level.FINEST ) )
542         {
543             log.log( Level.FINEST, session + " unwrapHandshake()" );
544         }
545         // Prepare the application buffer to receive decrypted data
546         appBuffer.clear();
547 
548         // Prepare the net data for reading.
549         inNetBuffer.flip();
550 
551         SSLEngineResult res;
552         do
553         {
554             if( log.isLoggable( Level.FINEST ) )
555             {
556                 log.log( Level.FINEST, session + "   inNetBuffer: " + inNetBuffer );
557                 log.log( Level.FINEST, session + "   appBuffer: " + appBuffer );
558             }
559             res = sslEngine.unwrap( inNetBuffer, appBuffer );
560             if( log.isLoggable( Level.FINEST ) )
561             {
562                 log.log( Level.FINEST, session + " Unwrap res:" + res );
563             }
564 
565         }
566         while( res.getStatus() == SSLEngineResult.Status.OK &&
567                res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP );
568 
569         initialHandshakeStatus = res.getHandshakeStatus();
570 	
571 	// If handshake finished, no data was produced, and the status is still ok,
572 		// try to unwrap more
573 		if (initialHandshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED
574 				&& appBuffer.position() == 0
575 				&& res.getStatus() == SSLEngineResult.Status.OK
576 				&& inNetBuffer.hasRemaining()) {
577 			do {
578 				if (log.isLoggable( Level.FINEST )) {
579 					log.log( Level.FINEST, session + "  extra handshake unwrap" );
580                     log.log( Level.FINEST, session + "   inNetBuffer: " + inNetBuffer );
581                     log.log( Level.FINEST, session + "   appBuffer: " + appBuffer );
582 				}
583 				res = sslEngine.unwrap(inNetBuffer, appBuffer);
584 				if (log.isLoggable( Level.FINEST )) {
585                     log.log( Level.FINEST, session + " Unwrap res:" + res );
586 				}
587 			} while (res.getStatus() == SSLEngineResult.Status.OK);
588 		}
589 
590         // If we are CLOSED, set flag
591         if( res.getStatus() == SSLEngineResult.Status.CLOSED )
592         {
593             closed = true;
594         }
595         
596         // prepare to be written again
597         inNetBuffer.compact();
598 
599         // prepare app data to be read
600         appBuffer.flip();
601 
602         /*
603          * The status may be:
604          * OK - Normal operation
605          * OVERFLOW - Should never happen since the application buffer is
606          *      sized to hold the maximum packet size.
607          * UNDERFLOW - Need to read more data from the socket. It's normal.
608          * CLOSED - The other peer closed the socket. Also normal.
609          */
610         //initialHandshakeStatus = res.getHandshakeStatus();
611         return checkStatus( res.getStatus() );
612     }
613 
614     /***
615      * Do all the outstanding handshake tasks in the current Thread.
616      */
617     private SSLEngineResult.HandshakeStatus doTasks()
618     {
619         if( log.isLoggable( Level.FINEST ) )
620         {
621             log.log( Level.FINEST, session + "   doTasks()" );
622         }
623 
624         /*
625          * We could run this in a separate thread, but I don't see the need
626          * for this when used from IoSSLFilter.Use thread filters in Mina instead?
627          */
628         Runnable runnable;
629         while( ( runnable = sslEngine.getDelegatedTask() ) != null )
630         {
631             if( log.isLoggable( Level.FINEST ) )
632             {
633                 log.log( Level.FINEST, session + "    doTask: " + runnable );
634             }
635             runnable.run();
636         }
637         if( log.isLoggable( Level.FINEST ) )
638         {
639             log.log( Level.FINEST, session + "   doTasks(): "
640                     + sslEngine.getHandshakeStatus() );
641         }
642         return sslEngine.getHandshakeStatus();
643     }
644 
645     /***
646      * Begin the shutdown process.
647      */
648     void doShutdown() throws SSLException
649     {
650 
651         if( !shutdown )
652         {
653             sslEngine.closeOutbound();
654             shutdown = true;
655         }
656 
657         // By RFC 2616, we can "fire and forget" our close_notify
658         // message, so that's what we'll do here.
659 
660         outNetBuffer.clear();
661         SSLEngineResult result = sslEngine.wrap( hsBB, outNetBuffer );
662         if( result.getStatus() != SSLEngineResult.Status.CLOSED )
663         {
664             throw new SSLException( "Improper close state: " + result );
665         }
666         outNetBuffer.flip();
667     }
668 }