EMMA Coverage Report (generated Tue Dec 20 11:01:01 KST 2005)
[all classes][org.apache.mina.io.filter]

COVERAGE SUMMARY FOR SOURCE FILE [SSLHandler.java]

nameclass, %method, %block, %line, %
SSLHandler.java100% (1/1)100% (24/24)50%  (561/1123)72%  (148.9/208)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class SSLHandler100% (1/1)100% (24/24)50%  (561/1123)72%  (148.9/208)
dataRead (IoFilter$NextFilter, ByteBuffer): void 100% (1/1)25%  (21/85)50%  (7/14)
checkStatus (SSLEngineResult$Status): SSLEngineResult$Status 100% (1/1)33%  (11/33)67%  (2/3)
doDecrypt (): void 100% (1/1)33%  (11/33)50%  (4/8)
unwrapHandshake (): SSLEngineResult$Status 100% (1/1)36%  (70/195)50%  (13/26)
doTasks (): SSLEngineResult$HandshakeStatus 100% (1/1)36%  (22/61)70%  (7/10)
continueHandshake (IoFilter$NextFilter): void 100% (1/1)39%  (7/18)75%  (3/4)
doEncrypt (ByteBuffer): void 100% (1/1)39%  (45/114)56%  (10/18)
doHandshake (IoFilter$NextFilter): void 100% (1/1)47%  (102/216)70%  (28/40)
unwrap (): SSLEngineResult$Status 100% (1/1)49%  (49/101)75%  (12/16)
flushScheduledWrites (): void 100% (1/1)69%  (29/42)88%  (7/8)
doShutdown (): void 100% (1/1)71%  (30/42)89%  (8/9)
SSLHandler (SSLFilter, SSLContext, IoSession): void 100% (1/1)83%  (91/109)87%  (26/30)
<static initializer> 100% (1/1)91%  (10/11)91%  (0.9/1)
encrypt (ByteBuffer): void 100% (1/1)100% (4/4)100% (2/2)
getAppBuffer (): ByteBuffer 100% (1/1)100% (3/3)100% (1/1)
getOutNetBuffer (): ByteBuffer 100% (1/1)100% (3/3)100% (1/1)
isClosed (): boolean 100% (1/1)100% (3/3)100% (1/1)
isInitialHandshakeComplete (): boolean 100% (1/1)100% (3/3)100% (1/1)
isWritingEncryptedData (): boolean 100% (1/1)100% (3/3)100% (1/1)
needToCompleteInitialHandshake (): boolean 100% (1/1)100% (11/11)100% (1/1)
release (): void 100% (1/1)100% (10/10)100% (4/4)
scheduleWrite (IoFilter$NextFilter, ByteBuffer, Object): void 100% (1/1)100% (13/13)100% (4/4)
setWritingEncryptedData (boolean): void 100% (1/1)100% (4/4)100% (2/2)
shutdown (): void 100% (1/1)100% (6/6)100% (3/3)

1/*
2 *   @(#) $Id: SSLHandler.java 357871 2005-12-20 01:56:40Z 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 */
19package org.apache.mina.io.filter;
20 
21import java.nio.ByteBuffer;
22 
23import javax.net.ssl.SSLContext;
24import javax.net.ssl.SSLEngine;
25import javax.net.ssl.SSLEngineResult;
26import javax.net.ssl.SSLException;
27import javax.net.ssl.SSLSession;
28 
29import org.apache.mina.io.IoSession;
30import org.apache.mina.io.IoFilter.NextFilter;
31import org.apache.mina.util.Queue;
32import org.slf4j.Logger;
33import org.slf4j.LoggerFactory;
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 The Apache Directory Project (dev@directory.apache.org)
44 * @version $Rev: 357871 $, $Date: 2005-12-20 10:56:40 +0900 (Tue, 20 Dec 2005) $
45 */
46class SSLHandler
47{
48    private static final Logger log = LoggerFactory.getLogger( SSLFilter.class );
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.isDebugEnabled() )
206            {
207                log.debug( 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.isDebugEnabled() )
234            {
235                log.debug( session + 
236                                    " expanded inNetBuffer:" + inNetBuffer );
237                log.debug( 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.isDebugEnabled() )
262        {
263            log.debug( 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.isDebugEnabled() ) {
338                 log.debug( 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.isDebugEnabled() ) {
387                    log.debug( session + " expanded outNetBuffer:" + outNetBuffer );
388                }
389            }
390 
391            result = sslEngine.wrap( src, outNetBuffer );
392            if ( log.isDebugEnabled() ) {
393                log.debug( 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.isDebugEnabled() )
417        {
418            log.debug( 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.isDebugEnabled() )
427                {
428                    SSLSession sslSession = sslEngine.getSession();
429                    log.debug( session + "  initialHandshakeStatus=FINISHED" );
430                    log.debug( 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.isDebugEnabled() )
438                {
439                    log.debug( 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.isDebugEnabled() )
447                {
448                    log.debug( 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.isDebugEnabled() )
463                {
464                    log.debug( 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.isDebugEnabled() )
471                    {
472                        log.debug( session + "  Still data in out buffer!" );
473                    }
474                    return;
475                }
476                outNetBuffer.clear();
477                SSLEngineResult result = sslEngine.wrap( hsBB, outNetBuffer );
478                if( log.isDebugEnabled() )
479                {
480                    log.debug( 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.isDebugEnabled() )
500        {
501            log.debug( 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.isDebugEnabled() )
513            {
514                log.debug( session + "   inNetBuffer: " + inNetBuffer );
515                log.debug( session + "   appBuffer: " + appBuffer );
516            }
517            res = sslEngine.unwrap( inNetBuffer, appBuffer );
518            if( log.isDebugEnabled() )
519            {
520                log.debug( 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.isDebugEnabled() )
550        {
551            log.debug( 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.isDebugEnabled() )
563            {
564                log.debug( session + "   inNetBuffer: " + inNetBuffer );
565                log.debug( session + "   appBuffer: " + appBuffer );
566            }
567            res = sslEngine.unwrap( inNetBuffer, appBuffer );
568            if( log.isDebugEnabled() )
569            {
570                log.debug( 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.isDebugEnabled()) {
587                                        log.debug( session + "  extra handshake unwrap" );
588                    log.debug( session + "   inNetBuffer: " + inNetBuffer );
589                    log.debug( session + "   appBuffer: " + appBuffer );
590                                }
591                                res = sslEngine.unwrap(inNetBuffer, appBuffer);
592                                if (log.isDebugEnabled()) {
593                    log.debug( 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.isDebugEnabled() )
628        {
629            log.debug( 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.isDebugEnabled() )
640            {
641                log.debug( session + "    doTask: " + runnable );
642            }
643            runnable.run();
644        }
645        if( log.isDebugEnabled() )
646        {
647            log.debug( 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}

[all classes][org.apache.mina.io.filter]
EMMA 2.0.4217 (C) Vladimir Roubtsov