KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > ChannelIOSecure


1 /*
2  * @(#)ChannelIOSecure.java 1.4 06/04/11
3  *
4  * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * -Redistribution of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  *
12  * -Redistribution in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * Neither the name of Sun Microsystems, Inc. or the names of contributors may
17  * be used to endorse or promote products derived from this software without
18  * specific prior written permission.
19  *
20  * This software is provided "AS IS," without a warranty of any kind. ALL
21  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
22  * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23  * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
24  * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
25  * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
26  * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
27  * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
28  * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
29  * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
30  * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31  *
32  * You acknowledge that this software is not designed, licensed or intended
33  * for use in the design, construction, operation or maintenance of any
34  * nuclear facility.
35  */

36
37 import java.io.*;
38 import java.nio.*;
39 import java.nio.channels.*;
40 import javax.net.ssl.*;
41 import javax.net.ssl.SSLEngineResult.*;
42
43 /**
44  * A helper class which performs I/O using the SSLEngine API.
45  * <P>
46  * Each connection has a SocketChannel and a SSLEngine that is
47  * used through the lifetime of the Channel. We allocate byte buffers
48  * for use as the outbound and inbound network buffers.
49  *
50  * <PRE>
51  * Application Data
52  * src requestBB
53  * | ^
54  * | | |
55  * v | |
56  * +----+-----|-----+----+
57  * | | |
58  * | SSL|Engine |
59  * wrap() | | | unwrap()
60  * | OUTBOUND | INBOUND |
61  * | | |
62  * +----+-----|-----+----+
63  * | | ^
64  * | | |
65  * v |
66  * outNetBB inNetBB
67  * Net data
68  * </PRE>
69  *
70  * These buffers handle all of the intermediary data for the SSL
71  * connection. To make things easy, we'll require outNetBB be
72  * completely flushed before trying to wrap any more data, but we
73  * could certainly remove that restriction by using larger buffers.
74  * <P>
75  * There are many, many ways to handle compute and I/O strategies.
76  * What follows is a relatively simple one. The reader is encouraged
77  * to develop the strategy that best fits the application.
78  * <P>
79  * In most of the non-blocking operations in this class, we let the
80  * Selector tell us when we're ready to attempt an I/O operation (by the
81  * application repeatedly calling our methods). Another option would be
82  * to attempt the operation and return from the method when no forward
83  * progress can be made.
84  * <P>
85  * There's lots of room for enhancements and improvement in this example.
86  * <P>
87  * We're checking for SSL/TLS end-of-stream truncation attacks via
88  * sslEngine.closeInbound(). When you reach the end of a input stream
89  * via a read() returning -1 or an IOException, we call
90  * sslEngine.closeInbound() to signal to the sslEngine that no more
91  * input will be available. If the peer's close_notify message has not
92  * yet been received, this could indicate a trucation attack, in which
93  * an attacker is trying to prematurely close the connection. The
94  * closeInbound() will throw an exception if this condition were
95  * present.
96  *
97  * @author Brad R. Wetmore
98  * @author Mark Reinhold
99  * @version 1.4, 06/04/11
100  */

101 class ChannelIOSecure extends ChannelIO {
102
103     private SSLEngine sslEngine = null;
104
105     private int appBBSize;
106     private int netBBSize;
107
108     /*
109      * All I/O goes through these buffers.
110      * <P>
111      * It might be nice to use a cache of ByteBuffers so we're
112      * not alloc/dealloc'ing ByteBuffer's for each new SSLEngine.
113      * <P>
114      * We use our superclass' requestBB for our application input buffer.
115      * Outbound application data is supplied to us by our callers.
116      */

117     private ByteBuffer inNetBB;
118     private ByteBuffer outNetBB;
119
120     /*
121      * An empty ByteBuffer for use when one isn't available, say
122      * as a source buffer during initial handshake wraps or for close
123      * operations.
124      */

125     private static ByteBuffer hsBB = ByteBuffer.allocate(0);
126
127     /*
128      * The FileChannel we're currently transferTo'ing (reading).
129      */

130     private ByteBuffer fileChannelBB = null;
131
132     /*
133      * During our initial handshake, keep track of the next
134      * SSLEngine operation that needs to occur:
135      *
136      * NEED_WRAP/NEED_UNWRAP
137      *
138      * Once the initial handshake has completed, we can short circuit
139      * handshake checks with initialHSComplete.
140      */

141     private HandshakeStatus initialHSStatus;
142     private boolean initialHSComplete;
143
144     /*
145      * We have received the shutdown request by our caller, and have
146      * closed our outbound side.
147      */

148     private boolean shutdown = false;
149
150     /*
151      * Constructor for a secure ChannelIO variant.
152      */

153     protected ChannelIOSecure(SocketChannel sc, boolean blocking,
154         SSLContext sslc) throws IOException {
155     super(sc, blocking);
156
157     /*
158      * We're a server, so no need to use host/port variant.
159      *
160      * The first call for a server is a NEED_UNWRAP.
161      */

162     sslEngine = sslc.createSSLEngine();
163     sslEngine.setUseClientMode(false);
164     initialHSStatus = HandshakeStatus.NEED_UNWRAP;
165     initialHSComplete = false;
166
167     // Create a buffer using the normal expected packet size we'll
168
// be getting. This may change, depending on the peer's
169
// SSL implementation.
170
netBBSize = sslEngine.getSession().getPacketBufferSize();
171     inNetBB = ByteBuffer.allocate(netBBSize);
172     outNetBB = ByteBuffer.allocate(netBBSize);
173     outNetBB.position(0);
174     outNetBB.limit(0);
175     }
176
177     /*
178      * Static factory method for creating a secure ChannelIO object.
179      * <P>
180      * We need to allocate different sized application data buffers
181      * based on whether we're secure or not. We can't determine
182      * this until our sslEngine is created.
183      */

184     static ChannelIOSecure getInstance(SocketChannel sc, boolean blocking,
185         SSLContext sslc) throws IOException {
186
187     ChannelIOSecure cio = new ChannelIOSecure(sc, blocking, sslc);
188
189     // Create a buffer using the normal expected application size we'll
190
// be getting. This may change, depending on the peer's
191
// SSL implementation.
192
cio.appBBSize = cio.sslEngine.getSession().getApplicationBufferSize();
193     cio.requestBB = ByteBuffer.allocate(cio.appBBSize);
194
195     return cio;
196     }
197
198     /*
199      * Calls up to the superclass to adjust the buffer size
200      * by an appropriate increment.
201      */

202     protected void resizeRequestBB() {
203     resizeRequestBB(appBBSize);
204     }
205
206     /*
207      * Adjust the inbount network buffer to an appropriate size.
208      */

209     private void resizeResponseBB() {
210     ByteBuffer bb = ByteBuffer.allocate(netBBSize);
211     inNetBB.flip();
212     bb.put(inNetBB);
213     inNetBB = bb;
214     }
215
216     /*
217      * Writes bb to the SocketChannel.
218      * <P>
219      * Returns true when the ByteBuffer has no remaining data.
220      */

221     private boolean tryFlush(ByteBuffer bb) throws IOException {
222     super.write(bb);
223     return !bb.hasRemaining();
224     }
225
226     /*
227      * Perform any handshaking processing.
228      * <P>
229      * This variant is for Servers without SelectionKeys (e.g.
230      * blocking).
231      */

232     boolean doHandshake() throws IOException {
233     return doHandshake(null);
234     }
235
236     /*
237      * Perform any handshaking processing.
238      * <P>
239      * If a SelectionKey is passed, register for selectable
240      * operations.
241      * <P>
242      * In the blocking case, our caller will keep calling us until
243      * we finish the handshake. Our reads/writes will block as expected.
244      * <P>
245      * In the non-blocking case, we just received the selection notification
246      * that this channel is ready for whatever the operation is, so give
247      * it a try.
248      * <P>
249      * return:
250      * true when handshake is done.
251      * false while handshake is in progress
252      */

253     boolean doHandshake(SelectionKey sk) throws IOException {
254
255     SSLEngineResult result;
256
257     if (initialHSComplete) {
258         return initialHSComplete;
259     }
260
261     /*
262      * Flush out the outgoing buffer, if there's anything left in
263      * it.
264      */

265     if (outNetBB.hasRemaining()) {
266
267         if (!tryFlush(outNetBB)) {
268         return false;
269         }
270
271         // See if we need to switch from write to read mode.
272

273         switch (initialHSStatus) {
274
275         /*
276          * Is this the last buffer?
277          */

278         case FINISHED:
279         initialHSComplete = true;
280         // Fall-through to reregister need for a Read.
281

282         case NEED_UNWRAP:
283         if (sk != null) {
284             sk.interestOps(SelectionKey.OP_READ);
285         }
286         break;
287         }
288
289         return initialHSComplete;
290     }
291
292
293     switch (initialHSStatus) {
294
295     case NEED_UNWRAP:
296         if (sc.read(inNetBB) == -1) {
297         sslEngine.closeInbound();
298         return initialHSComplete;
299         }
300
301 needIO:
302         while (initialHSStatus == HandshakeStatus.NEED_UNWRAP) {
303         resizeRequestBB(); // expected room for unwrap
304
inNetBB.flip();
305         result = sslEngine.unwrap(inNetBB, requestBB);
306         inNetBB.compact();
307
308         initialHSStatus = result.getHandshakeStatus();
309
310         switch (result.getStatus()) {
311
312         case OK:
313             switch (initialHSStatus) {
314             case NOT_HANDSHAKING:
315             throw new IOException(
316                 "Not handshaking during initial handshake");
317
318             case NEED_TASK:
319             initialHSStatus = doTasks();
320             break;
321
322             case FINISHED:
323             initialHSComplete = true;
324             break needIO;
325             }
326
327             break;
328
329         case BUFFER_UNDERFLOW:
330             // Resize buffer if needed.
331
netBBSize = sslEngine.getSession().getPacketBufferSize();
332             if (netBBSize > inNetBB.capacity()) {
333             resizeResponseBB();
334             }
335
336             /*
337              * Need to go reread the Channel for more data.
338              */

339             if (sk != null) {
340             sk.interestOps(SelectionKey.OP_READ);
341             }
342             break needIO;
343
344         case BUFFER_OVERFLOW:
345             // Reset the application buffer size.
346
appBBSize =
347             sslEngine.getSession().getApplicationBufferSize();
348             break;
349
350         default: //CLOSED:
351
throw new IOException("Received" + result.getStatus() +
352             "during initial handshaking");
353         }
354         } // "needIO" block.
355

356         /*
357          * Just transitioned from read to write.
358          */

359         if (initialHSStatus != HandshakeStatus.NEED_WRAP) {
360         break;
361         }
362
363         // Fall through and fill the write buffers.
364

365     case NEED_WRAP:
366         /*
367          * The flush above guarantees the out buffer to be empty
368          */

369         outNetBB.clear();
370         result = sslEngine.wrap(hsBB, outNetBB);
371         outNetBB.flip();
372
373         initialHSStatus = result.getHandshakeStatus();
374
375         switch (result.getStatus()) {
376         case OK:
377
378         if (initialHSStatus == HandshakeStatus.NEED_TASK) {
379             initialHSStatus = doTasks();
380         }
381
382         if (sk != null) {
383             sk.interestOps(SelectionKey.OP_WRITE);
384         }
385
386         break;
387
388         default: // BUFFER_OVERFLOW/BUFFER_UNDERFLOW/CLOSED:
389
throw new IOException("Received" + result.getStatus() +
390             "during initial handshaking");
391         }
392         break;
393
394     default: // NOT_HANDSHAKING/NEED_TASK/FINISHED
395
throw new RuntimeException JavaDoc("Invalid Handshaking State" +
396             initialHSStatus);
397     } // switch
398

399     return initialHSComplete;
400     }
401
402     /*
403      * Do all the outstanding handshake tasks in the current Thread.
404      */

405     private SSLEngineResult.HandshakeStatus doTasks() {
406
407     Runnable JavaDoc runnable;
408
409     /*
410      * We could run this in a separate thread, but
411      * do in the current for now.
412      */

413     while ((runnable = sslEngine.getDelegatedTask()) != null) {
414         runnable.run();
415     }
416     return sslEngine.getHandshakeStatus();
417     }
418
419     /*
420      * Read the channel for more information, then unwrap the
421      * (hopefully application) data we get.
422      * <P>
423      * If we run out of data, we'll return to our caller (possibly using
424      * a Selector) to get notification that more is available.
425      * <P>
426      * Each call to this method will perform at most one underlying read().
427      */

428     int read() throws IOException {
429     SSLEngineResult result;
430
431     if (!initialHSComplete) {
432         throw new IllegalStateException JavaDoc();
433     }
434
435     int pos = requestBB.position();
436
437     if (sc.read(inNetBB) == -1) {
438         sslEngine.closeInbound(); // probably throws exception
439
return -1;
440     }
441
442     do {
443         resizeRequestBB(); // expected room for unwrap
444
inNetBB.flip();
445         result = sslEngine.unwrap(inNetBB, requestBB);
446         inNetBB.compact();
447
448         /*
449          * Could check here for a renegotation, but we're only
450          * doing a simple read/write, and won't have enough state
451          * transitions to do a complete handshake, so ignore that
452          * possibility.
453          */

454         switch (result.getStatus()) {
455
456         case BUFFER_OVERFLOW:
457         // Reset the application buffer size.
458
appBBSize = sslEngine.getSession().getApplicationBufferSize();
459         break;
460         
461         case BUFFER_UNDERFLOW:
462         // Resize buffer if needed.
463
netBBSize = sslEngine.getSession().getPacketBufferSize();
464         if (netBBSize > inNetBB.capacity()) {
465             resizeResponseBB();
466
467             break; // break, next read will support larger buffer.
468
}
469         case OK:
470         if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
471             doTasks();
472         }
473         break;
474
475         default:
476         throw new IOException("sslEngine error during data read: " +
477             result.getStatus());
478         }
479     } while ((inNetBB.position() != 0) &&
480         result.getStatus() != Status.BUFFER_UNDERFLOW);
481
482     return (requestBB.position() - pos);
483     }
484
485     /*
486      * Try to write out as much as possible from the src buffer.
487      */

488     int write(ByteBuffer src) throws IOException {
489
490     if (!initialHSComplete) {
491         throw new IllegalStateException JavaDoc();
492     }
493
494     return doWrite(src);
495     }
496
497     /*
498      * Try to flush out any existing outbound data, then try to wrap
499      * anything new contained in the src buffer.
500      * <P>
501      * Return the number of bytes actually consumed from the buffer,
502      * but the data may actually be still sitting in the output buffer,
503      * waiting to be flushed.
504      */

505     private int doWrite(ByteBuffer src) throws IOException {
506     int retValue = 0;
507
508     if (outNetBB.hasRemaining() && !tryFlush(outNetBB)) {
509         return retValue;
510     }
511
512     /*
513      * The data buffer is empty, we can reuse the entire buffer.
514      */

515     outNetBB.clear();
516
517     SSLEngineResult result = sslEngine.wrap(src, outNetBB);
518     retValue = result.bytesConsumed();
519
520     outNetBB.flip();
521
522     switch (result.getStatus()) {
523
524     case OK:
525         if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
526         doTasks();
527         }
528         break;
529
530     default:
531         throw new IOException("sslEngine error during data write: " +
532         result.getStatus());
533     }
534
535     /*
536      * Try to flush the data, regardless of whether or not
537      * it's been selected. Odds of a write buffer being full
538      * is less than a read buffer being empty.
539      */

540     if (outNetBB.hasRemaining()) {
541         tryFlush(outNetBB);
542     }
543
544     return retValue;
545     }
546
547     /*
548      * Perform a FileChannel.TransferTo on the socket channel.
549      * <P>
550      * We have to copy the data into an intermediary app ByteBuffer
551      * first, then send it through the SSLEngine.
552      * <P>
553      * We return the number of bytes actually read out of the
554      * filechannel. However, the data may actually be stuck
555      * in the fileChannelBB or the outNetBB. The caller
556      * is responsible for making sure to call dataFlush()
557      * before shutting down.
558      */

559     long transferTo(FileChannel fc, long pos, long len) throws IOException {
560
561     if (!initialHSComplete) {
562         throw new IllegalStateException JavaDoc();
563     }
564
565     if (fileChannelBB == null) {
566         fileChannelBB = ByteBuffer.allocate(appBBSize);
567         fileChannelBB.limit(0);
568     }
569
570     fileChannelBB.compact();
571     int fileRead = fc.read(fileChannelBB);
572     fileChannelBB.flip();
573
574     /*
575      * We ignore the return value here, we return the
576      * number of bytes actually consumed from the the file.
577      * We'll flush the output buffer before we start shutting down.
578      */

579     doWrite(fileChannelBB);
580
581     return fileRead;
582     }
583
584     /*
585      * Flush any remaining data.
586      * <P>
587      * Return true when the fileChannelBB and outNetBB are empty.
588      */

589     boolean dataFlush() throws IOException {
590     boolean fileFlushed = true;
591
592     if ((fileChannelBB != null) && fileChannelBB.hasRemaining()) {
593         doWrite(fileChannelBB);
594         fileFlushed = !fileChannelBB.hasRemaining();
595     } else if (outNetBB.hasRemaining()) {
596         tryFlush(outNetBB);
597     }
598
599     return (fileFlushed && !outNetBB.hasRemaining());
600     }
601
602     /*
603      * Begin the shutdown process.
604      * <P>
605      * Close out the SSLEngine if not already done so, then
606      * wrap our outgoing close_notify message and try to send it on.
607      * <P>
608      * Return true when we're done passing the shutdown messsages.
609      */

610     boolean shutdown() throws IOException {
611
612     if (!shutdown) {
613         sslEngine.closeOutbound();
614         shutdown = true;
615     }
616
617     if (outNetBB.hasRemaining() && tryFlush(outNetBB)) {
618         return false;
619     }
620
621     /*
622      * By RFC 2616, we can "fire and forget" our close_notify
623      * message, so that's what we'll do here.
624      */

625     outNetBB.clear();
626     SSLEngineResult result = sslEngine.wrap(hsBB, outNetBB);
627     if (result.getStatus() != Status.CLOSED) {
628         throw new SSLException("Improper close state");
629     }
630     outNetBB.flip();
631
632     /*
633      * We won't wait for a select here, but if this doesn't work,
634      * we'll cycle back through on the next select.
635      */

636     if (outNetBB.hasRemaining()) {
637         tryFlush(outNetBB);
638     }
639
640     return (!outNetBB.hasRemaining() &&
641         (result.getHandshakeStatus() != HandshakeStatus.NEED_WRAP));
642     }
643
644     /*
645      * close() is not overridden
646      */

647 }
648
Popular Tags