1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
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
227 inNetBuffer = SSLByteBufferPool.expandBuffer( inNetBuffer,
228 inNetBuffer.capacity() + ( buf.limit() * 2 ) );
229
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
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
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
373
374 outNetBuffer.clear();
375
376 SSLEngineResult result;
377
378
379 while ( src.hasRemaining() ) {
380
381 if ( src.remaining() > ( ( outNetBuffer.capacity() - outNetBuffer.position() ) / 2 ) ) {
382
383
384
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
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
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
467
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
487
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
504 appBuffer.clear();
505
506
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
526 if( res.getStatus() == SSLEngineResult.Status.CLOSED )
527 {
528 closed = true;
529 }
530
531
532 inNetBuffer.compact();
533
534 appBuffer.flip();
535
536
537
538
539
540
541
542
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
554 appBuffer.clear();
555
556
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
580
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
599 if( res.getStatus() == SSLEngineResult.Status.CLOSED )
600 {
601 closed = true;
602 }
603
604
605 inNetBuffer.compact();
606
607
608 appBuffer.flip();
609
610
611
612
613
614
615
616
617
618
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
634
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
666
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 }