View Javadoc

1   /*
2    *   @(#) $Id: SSLHandler.java 210062 2005-07-11 03:52:38Z 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: 210062 $, $Date: 2005-07-11 12:52:38 +0900 $
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 final 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 
115         if ( parent.isWantClientAuth() )
116         {
117             sslEngine.setWantClientAuth( true );
118         }
119 
120         if ( parent.isNeedClientAuth() )
121         {
122             sslEngine.setNeedClientAuth( true );
123         }
124   
125         if( parent.getEnabledCipherSuites() != null )
126         {
127             sslEngine.setEnabledCipherSuites( parent.getEnabledCipherSuites() );
128         }
129         
130         if( parent.getEnabledProtocols() != null )
131         {
132             sslEngine.setEnabledProtocols( parent.getEnabledProtocols() );
133         }
134 
135         sslEngine.beginHandshake();   
136         initialHandshakeStatus = sslEngine.getHandshakeStatus();//SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
137         initialHandshakeComplete = false;
138         
139         SSLByteBufferPool.initiate( sslEngine );
140 
141         appBuffer = SSLByteBufferPool.getApplicationBuffer();
142 
143         inNetBuffer = SSLByteBufferPool.getPacketBuffer();
144         outNetBuffer = SSLByteBufferPool.getPacketBuffer();
145         outNetBuffer.position( 0 );
146         outNetBuffer.limit( 0 );
147     }
148 
149     /***
150      * Indicate that we are writing encrypted data.
151      * Only used as a flag by IoSSLFiler
152      */
153     public void setWritingEncryptedData( boolean flag )
154     {
155         isWritingEncryptedData = flag;
156     }
157 
158     /***
159      * Check we are writing encrypted data.
160      */
161     public boolean isWritingEncryptedData()
162     {
163         return isWritingEncryptedData;
164     }
165 
166     /***
167      * Check if initial handshake is completed.
168      */
169     public boolean isInitialHandshakeComplete()
170     {
171         return initialHandshakeComplete;
172     }
173 
174     /***
175      * Check if SSL sesssion closed
176      */
177     public boolean isClosed()
178     {
179         return closed;
180     }
181 
182     /***
183      * Check if there is any need to complete initial handshake.
184      */
185     public boolean needToCompleteInitialHandshake()
186     {
187         return ( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP && !closed );
188     }
189     
190     public synchronized void scheduleWrite( NextFilter nextFilter, org.apache.mina.common.ByteBuffer buf, Object marker )
191     {
192         nextFilterQueue.push( nextFilter );
193         writeBufferQueue.push( buf );
194         writeMarkerQueue.push( marker );
195     }
196     
197     public synchronized void flushScheduledWrites() throws SSLException
198     {
199         NextFilter nextFilter;
200         org.apache.mina.common.ByteBuffer scheduledBuf;
201         Object scheduledMarker;
202         
203         while( ( scheduledBuf = ( org.apache.mina.common.ByteBuffer ) writeBufferQueue.pop() ) != null )
204         {
205             if( log.isLoggable( Level.FINEST ) )
206             {
207                 log.log( Level.FINEST, session + " Flushing buffered write request: " + scheduledBuf );
208             }
209             nextFilter = ( NextFilter ) nextFilterQueue.pop();
210             scheduledMarker = writeMarkerQueue.pop();
211             parent.filterWrite( nextFilter, session, scheduledBuf, scheduledMarker );
212         }
213     }
214 
215     /***
216      * Call when data read from net. Will perform inial hanshake or decrypt provided
217      * Buffer.
218      * Decrytpted data reurned by getAppBuffer(), if any.
219      *
220      * @param buf buffer to decrypt
221      * @throws SSLException on errors
222      */
223     public void dataRead( NextFilter nextFilter, ByteBuffer buf ) throws SSLException
224     {
225         if ( buf.limit() > inNetBuffer.remaining() ) {
226             // We have to expand inNetBuffer
227             inNetBuffer = SSLByteBufferPool.expandBuffer( inNetBuffer,
228                 inNetBuffer.capacity() + ( buf.limit() * 2 ) );
229             // We also expand app. buffer (twice the size of in net. buffer)
230             appBuffer = SSLByteBufferPool.expandBuffer( appBuffer, inNetBuffer.capacity() * 2);
231             appBuffer.position( 0 );
232             appBuffer.limit( 0 );
233             if( log.isLoggable( Level.FINEST ) )
234             {
235                 log.log( Level.FINEST, session + 
236                                     " expanded inNetBuffer:" + inNetBuffer );
237                 log.log( Level.FINEST, session + 
238                                     " expanded appBuffer:" + appBuffer );
239             }
240         }
241 
242         // append buf to inNetBuffer
243         inNetBuffer.put( buf );
244         if( !initialHandshakeComplete )
245         {
246             doHandshake( nextFilter );
247         }
248         else
249         {
250             doDecrypt();
251         }
252     }
253 
254     /***
255      * Continue initial SSL handshake.
256      *
257      * @throws SSLException on errors
258      */
259     public void continueHandshake( NextFilter nextFilter ) throws SSLException
260     {
261         if( log.isLoggable( Level.FINEST ) )
262         {
263             log.log( Level.FINEST, session + " continueHandshake()" );
264         }
265         doHandshake( nextFilter );
266     }
267 
268     /***
269      * Get decrypted application data.
270      *
271      * @return buffer with data
272      */
273     public ByteBuffer getAppBuffer()
274     {
275         return appBuffer;
276     }
277 
278     /***
279      * Get encrypted data to be sent.
280      *
281      * @return buffer with data
282      */
283     public ByteBuffer getOutNetBuffer()
284     {
285         return outNetBuffer;
286     }
287 
288     /***
289      * Encrypt provided buffer. Encytpted data reurned by getOutNetBuffer().
290      *
291      * @param buf data to encrypt
292      * @throws SSLException on errors
293      */
294     public void encrypt( ByteBuffer buf ) throws SSLException
295     {
296         doEncrypt( buf );
297     }
298 
299     /***
300      * Start SSL shutdown process
301      *
302      * @throws SSLException on errors
303      */
304     public void shutdown() throws SSLException
305     {
306         if( !shutdown )
307         {
308             doShutdown();
309         }
310     }
311 
312     /***
313      * Release allocated ByteBuffers.
314      */
315     public void release()
316     {
317         SSLByteBufferPool.release( appBuffer );
318         SSLByteBufferPool.release( inNetBuffer );
319         SSLByteBufferPool.release( outNetBuffer );
320     }
321 
322     /***
323      * Decrypt in net buffer. Result is stored in app buffer.
324      *
325      * @throws SSLException
326      */
327     private void doDecrypt() throws SSLException
328     {
329 
330         if( !initialHandshakeComplete )
331         {
332             throw new IllegalStateException();
333         }
334 
335         if( appBuffer.hasRemaining() )
336         {
337              if ( log.isLoggable( Level.FINEST ) ) {
338                  log.log( Level.FINEST, session + " Error: appBuffer not empty!" );
339              }
340             //still app data in buffer!?
341             throw new IllegalStateException();
342         }
343 
344         unwrap();
345     }
346 
347     /***
348      * @param status
349      * @throws SSLException
350      */
351     private SSLEngineResult.Status checkStatus( SSLEngineResult.Status status ) throws SSLException
352     {
353         if( status != SSLEngineResult.Status.OK &&
354             status != SSLEngineResult.Status.CLOSED &&
355             status != SSLEngineResult.Status.BUFFER_UNDERFLOW )
356         {
357             throw new SSLException( "SSLEngine error during decrypt: " +
358                                     status +
359                                     " inNetBuffer: " + inNetBuffer + "appBuffer: " + appBuffer);
360         }
361         
362         return status;
363     }
364     
365     private void doEncrypt( ByteBuffer src ) throws SSLException
366     {
367         if( !initialHandshakeComplete )
368         {
369             throw new IllegalStateException();
370         }
371 
372         // The data buffer is (must be) empty, we can reuse the entire
373         // buffer.
374         outNetBuffer.clear();
375 
376         SSLEngineResult result;
377 
378         // Loop until there is no more data in src
379         while ( src.hasRemaining() ) {
380 
381             if ( src.remaining() > ( ( outNetBuffer.capacity() - outNetBuffer.position() ) / 2 ) ) {
382                 // We have to expand outNetBuffer
383                 // Note: there is no way to know the exact size required, but enrypted data
384                 // shouln't need to be larger than twice the source data size?
385                 outNetBuffer = SSLByteBufferPool.expandBuffer( outNetBuffer, src.capacity() * 2 );
386                 if ( log.isLoggable( Level.FINEST ) ) {
387                     log.log( Level.FINEST, session + " expanded outNetBuffer:" + outNetBuffer );
388                 }
389             }
390 
391             result = sslEngine.wrap( src, outNetBuffer );
392             if ( log.isLoggable( Level.FINEST ) ) {
393                 log.log( Level.FINEST, session + " Wrap res:" + result );
394             }
395 
396             if ( result.getStatus() == SSLEngineResult.Status.OK ) {
397                 if ( result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK ) {
398                     doTasks();
399                 }
400             } else {
401                 throw new SSLException( "SSLEngine error during encrypt: "
402                         + result.getStatus() +
403                         " src: " + src + "outNetBuffer: " + outNetBuffer);
404             }
405         }
406 
407         outNetBuffer.flip();
408     }
409 
410     /***
411      * Perform any handshaking processing.
412      */
413     synchronized void doHandshake( NextFilter nextFilter ) throws SSLException
414     {
415 
416         if( log.isLoggable( Level.FINEST ) )
417         {
418             log.log( Level.FINEST, session + " doHandshake()" );
419         }
420 
421         while( !initialHandshakeComplete )
422         {
423             if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED )
424             {
425                 session.setAttribute( SSLFilter.SSL_SESSION, sslEngine.getSession() );
426                 if( log.isLoggable( Level.FINEST ) )
427                 {
428                     SSLSession sslSession = sslEngine.getSession();
429                     log.log( Level.FINEST, session + "  initialHandshakeStatus=FINISHED" );
430                     log.log( Level.FINEST, session + "  sslSession CipherSuite used " + sslSession.getCipherSuite() );
431                 }
432                 initialHandshakeComplete = true;
433                 return;
434             }
435             else if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK )
436             {
437                 if( log.isLoggable( Level.FINEST ) )
438                 {
439                     log.log( Level.FINEST, session + "  initialHandshakeStatus=NEED_TASK" );
440                 }
441                 initialHandshakeStatus = doTasks();
442             }
443             else if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP )
444             {
445                 // we need more data read
446                 if( log.isLoggable( Level.FINEST ) )
447                 {
448                     log.log( Level.FINEST, session +
449                              "  initialHandshakeStatus=NEED_UNWRAP" );
450                 }
451                 SSLEngineResult.Status status = unwrapHandshake();
452                 if( ( initialHandshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED 
453                 		&&  status == SSLEngineResult.Status.BUFFER_UNDERFLOW )
454                         || closed )
455                 {
456                     // We need more data or the session is closed
457                     return;
458                 }
459             }
460             else if( initialHandshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP )
461             {
462                 if( log.isLoggable( Level.FINEST ) )
463                 {
464                     log.log( Level.FINEST, session + "  initialHandshakeStatus=NEED_WRAP" );
465                 }
466                 // First make sure that the out buffer is completely empty. Since we
467                 // cannot call wrap with data left on the buffer
468                 if( outNetBuffer.hasRemaining() )
469                 {
470                     if( log.isLoggable( Level.FINEST ) )
471                     {
472                         log.log( Level.FINEST, session + "  Still data in out buffer!" );
473                     }
474                     return;
475                 }
476                 outNetBuffer.clear();
477                 SSLEngineResult result = sslEngine.wrap( hsBB, outNetBuffer );
478                 if( log.isLoggable( Level.FINEST ) )
479                 {
480                     log.log( Level.FINEST, session + " Wrap res:" + result );
481                 }
482 
483                 outNetBuffer.flip();
484                 initialHandshakeStatus = result.getHandshakeStatus();
485                 parent.writeNetBuffer( nextFilter, session, this );
486                 // return to allow data on out buffer being sent
487                 // TODO: We might want to send more data immidiatley?
488             }
489             else
490             {
491                 throw new IllegalStateException( "Invalid Handshaking State"
492                         + initialHandshakeStatus );
493             }
494         }
495     }
496 
497     SSLEngineResult.Status unwrap() throws SSLException
498     {
499         if( log.isLoggable( Level.FINEST ) )
500         {
501             log.log( Level.FINEST, session + " unwrap()" );
502         }
503         // Prepare the application buffer to receive decrypted data
504         appBuffer.clear();
505 
506         // Prepare the net data for reading.
507         inNetBuffer.flip();
508 
509         SSLEngineResult res;
510         do
511         {
512             if( log.isLoggable( Level.FINEST ) )
513             {
514                 log.log( Level.FINEST, session + "   inNetBuffer: " + inNetBuffer );
515                 log.log( Level.FINEST, session + "   appBuffer: " + appBuffer );
516             }
517             res = sslEngine.unwrap( inNetBuffer, appBuffer );
518             if( log.isLoggable( Level.FINEST ) )
519             {
520                 log.log( Level.FINEST, session + " Unwrap res:" + res );
521             }
522         }
523         while( res.getStatus() == SSLEngineResult.Status.OK );
524 
525         // If we are CLOSED, set flag
526         if( res.getStatus() == SSLEngineResult.Status.CLOSED )
527         {
528             closed = true;
529         }
530 
531         // prepare to be written again
532         inNetBuffer.compact();
533         // prepare app data to be read
534         appBuffer.flip();
535 
536         /*
537          * The status may be:
538          * OK - Normal operation
539          * OVERFLOW - Should never happen since the application buffer is
540          *      sized to hold the maximum packet size.
541          * UNDERFLOW - Need to read more data from the socket. It's normal.
542          * CLOSED - The other peer closed the socket. Also normal.
543          */
544         return checkStatus( res.getStatus() );
545     }
546 
547     private SSLEngineResult.Status unwrapHandshake() throws SSLException
548     {
549         if( log.isLoggable( Level.FINEST ) )
550         {
551             log.log( Level.FINEST, session + " unwrapHandshake()" );
552         }
553         // Prepare the application buffer to receive decrypted data
554         appBuffer.clear();
555 
556         // Prepare the net data for reading.
557         inNetBuffer.flip();
558 
559         SSLEngineResult res;
560         do
561         {
562             if( log.isLoggable( Level.FINEST ) )
563             {
564                 log.log( Level.FINEST, session + "   inNetBuffer: " + inNetBuffer );
565                 log.log( Level.FINEST, session + "   appBuffer: " + appBuffer );
566             }
567             res = sslEngine.unwrap( inNetBuffer, appBuffer );
568             if( log.isLoggable( Level.FINEST ) )
569             {
570                 log.log( Level.FINEST, session + " Unwrap res:" + res );
571             }
572 
573         }
574         while( res.getStatus() == SSLEngineResult.Status.OK &&
575                res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP );
576 
577         initialHandshakeStatus = res.getHandshakeStatus();
578 	
579 	// If handshake finished, no data was produced, and the status is still ok,
580 		// try to unwrap more
581 		if (initialHandshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED
582 				&& appBuffer.position() == 0
583 				&& res.getStatus() == SSLEngineResult.Status.OK
584 				&& inNetBuffer.hasRemaining()) {
585 			do {
586 				if (log.isLoggable( Level.FINEST )) {
587 					log.log( Level.FINEST, session + "  extra handshake unwrap" );
588                     log.log( Level.FINEST, session + "   inNetBuffer: " + inNetBuffer );
589                     log.log( Level.FINEST, session + "   appBuffer: " + appBuffer );
590 				}
591 				res = sslEngine.unwrap(inNetBuffer, appBuffer);
592 				if (log.isLoggable( Level.FINEST )) {
593                     log.log( Level.FINEST, session + " Unwrap res:" + res );
594 				}
595 			} while (res.getStatus() == SSLEngineResult.Status.OK);
596 		}
597 
598         // If we are CLOSED, set flag
599         if( res.getStatus() == SSLEngineResult.Status.CLOSED )
600         {
601             closed = true;
602         }
603         
604         // prepare to be written again
605         inNetBuffer.compact();
606 
607         // prepare app data to be read
608         appBuffer.flip();
609 
610         /*
611          * The status may be:
612          * OK - Normal operation
613          * OVERFLOW - Should never happen since the application buffer is
614          *      sized to hold the maximum packet size.
615          * UNDERFLOW - Need to read more data from the socket. It's normal.
616          * CLOSED - The other peer closed the socket. Also normal.
617          */
618         //initialHandshakeStatus = res.getHandshakeStatus();
619         return checkStatus( res.getStatus() );
620     }
621 
622     /***
623      * Do all the outstanding handshake tasks in the current Thread.
624      */
625     private SSLEngineResult.HandshakeStatus doTasks()
626     {
627         if( log.isLoggable( Level.FINEST ) )
628         {
629             log.log( Level.FINEST, session + "   doTasks()" );
630         }
631 
632         /*
633          * We could run this in a separate thread, but I don't see the need
634          * for this when used from IoSSLFilter.Use thread filters in Mina instead?
635          */
636         Runnable runnable;
637         while( ( runnable = sslEngine.getDelegatedTask() ) != null )
638         {
639             if( log.isLoggable( Level.FINEST ) )
640             {
641                 log.log( Level.FINEST, session + "    doTask: " + runnable );
642             }
643             runnable.run();
644         }
645         if( log.isLoggable( Level.FINEST ) )
646         {
647             log.log( Level.FINEST, session + "   doTasks(): "
648                     + sslEngine.getHandshakeStatus() );
649         }
650         return sslEngine.getHandshakeStatus();
651     }
652 
653     /***
654      * Begin the shutdown process.
655      */
656     void doShutdown() throws SSLException
657     {
658 
659         if( !shutdown )
660         {
661             sslEngine.closeOutbound();
662             shutdown = true;
663         }
664 
665         // By RFC 2616, we can "fire and forget" our close_notify
666         // message, so that's what we'll do here.
667 
668         outNetBuffer.clear();
669         SSLEngineResult result = sslEngine.wrap( hsBB, outNetBuffer );
670         if( result.getStatus() != SSLEngineResult.Status.CLOSED )
671         {
672             throw new SSLException( "Improper close state: " + result );
673         }
674         outNetBuffer.flip();
675     }
676 }