001: /*
002: * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/httpcore/tags/4.0-beta1/module-nio/src/main/java/org/apache/http/impl/nio/reactor/SSLIOSession.java $
003: * $Revision: 613013 $
004: * $Date: 2008-01-18 00:56:18 +0100 (Fri, 18 Jan 2008) $
005: *
006: * ====================================================================
007: * Licensed to the Apache Software Foundation (ASF) under one
008: * or more contributor license agreements. See the NOTICE file
009: * distributed with this work for additional information
010: * regarding copyright ownership. The ASF licenses this file
011: * to you under the Apache License, Version 2.0 (the
012: * "License"); you may not use this file except in compliance
013: * with the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing,
018: * software distributed under the License is distributed on an
019: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020: * KIND, either express or implied. See the License for the
021: * specific language governing permissions and limitations
022: * under the License.
023: * ====================================================================
024: *
025: * This software consists of voluntary contributions made by many
026: * individuals on behalf of the Apache Software Foundation. For more
027: * information on the Apache Software Foundation, please see
028: * <http://www.apache.org/>.
029: *
030: */
031:
032: package org.apache.http.impl.nio.reactor;
033:
034: import java.io.IOException;
035: import java.net.InetSocketAddress;
036: import java.net.SocketAddress;
037: import java.nio.ByteBuffer;
038: import java.nio.channels.ByteChannel;
039:
040: import javax.net.ssl.SSLContext;
041: import javax.net.ssl.SSLEngine;
042: import javax.net.ssl.SSLEngineResult;
043: import javax.net.ssl.SSLException;
044: import javax.net.ssl.SSLEngineResult.HandshakeStatus;
045: import javax.net.ssl.SSLEngineResult.Status;
046:
047: import org.apache.http.nio.reactor.EventMask;
048: import org.apache.http.nio.reactor.IOSession;
049: import org.apache.http.nio.reactor.SessionBufferStatus;
050: import org.apache.http.params.HttpParams;
051:
052: /**
053: * A decorator class intended to transparently extend an {@link IOSession}
054: * with transport layer security capabilities based on the SSL/TLS protocol.
055: *
056: * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
057: */
058: public class SSLIOSession implements IOSession, SessionBufferStatus {
059:
060: private final IOSession session;
061: private final SSLEngine sslEngine;
062: private final ByteBuffer inEncrypted;
063: private final ByteBuffer outEncrypted;
064: private final ByteBuffer inPlain;
065: private final ByteBuffer outPlain;
066: private final InternalByteChannel channel;
067: private final SSLIOSessionHandler handler;
068:
069: private int appEventMask;
070: private SessionBufferStatus appBufferStatus;
071:
072: private volatile int status;
073:
074: public SSLIOSession(final IOSession session,
075: final SSLContext sslContext,
076: final SSLIOSessionHandler handler) {
077: super ();
078: if (session == null) {
079: throw new IllegalArgumentException(
080: "IO session may not be null");
081: }
082: if (sslContext == null) {
083: throw new IllegalArgumentException(
084: "SSL context may not be null");
085: }
086: this .session = session;
087: this .appEventMask = session.getEventMask();
088: this .channel = new InternalByteChannel();
089: this .handler = handler;
090:
091: // Override the status buffer interface
092: this .session.setBufferStatus(this );
093:
094: SocketAddress address = session.getRemoteAddress();
095: if (address instanceof InetSocketAddress) {
096: String hostname = ((InetSocketAddress) address)
097: .getHostName();
098: int port = ((InetSocketAddress) address).getPort();
099: this .sslEngine = sslContext.createSSLEngine(hostname, port);
100: } else {
101: this .sslEngine = sslContext.createSSLEngine();
102: }
103:
104: // Allocate buffers for network (encrypted) data
105: int netBuffersize = this .sslEngine.getSession()
106: .getPacketBufferSize();
107: this .inEncrypted = ByteBuffer.allocateDirect(netBuffersize);
108: this .outEncrypted = ByteBuffer.allocateDirect(netBuffersize);
109:
110: // Allocate buffers for application (unencrypted) data
111: int appBuffersize = this .sslEngine.getSession()
112: .getApplicationBufferSize();
113: this .inPlain = ByteBuffer.allocateDirect(appBuffersize);
114: this .outPlain = ByteBuffer.allocateDirect(appBuffersize);
115: }
116:
117: public synchronized void bind(final SSLMode mode,
118: final HttpParams params) throws SSLException {
119: if (params == null) {
120: throw new IllegalArgumentException(
121: "HTTP parameters may not be null");
122: }
123: switch (mode) {
124: case CLIENT:
125: this .sslEngine.setUseClientMode(true);
126: break;
127: case SERVER:
128: this .sslEngine.setUseClientMode(false);
129: break;
130: }
131: if (this .handler != null) {
132: this .handler.initalize(this .sslEngine, params);
133: }
134: this .sslEngine.beginHandshake();
135: doHandshake();
136: }
137:
138: private void doHandshake() throws SSLException {
139: boolean handshaking = true;
140:
141: SSLEngineResult result = null;
142: while (handshaking) {
143: switch (this .sslEngine.getHandshakeStatus()) {
144: case NEED_WRAP:
145: // Generate outgoing handshake data
146: this .outPlain.flip();
147: result = this .sslEngine.wrap(this .outPlain,
148: this .outEncrypted);
149: this .outPlain.compact();
150: if (result.getStatus() != Status.OK) {
151: handshaking = false;
152: }
153: if (result.getStatus() == Status.CLOSED) {
154: this .status = CLOSED;
155: }
156: break;
157: case NEED_UNWRAP:
158: // Process incoming handshake data
159: this .inEncrypted.flip();
160: result = this .sslEngine.unwrap(this .inEncrypted,
161: this .inPlain);
162: this .inEncrypted.compact();
163: if (result.getStatus() != Status.OK) {
164: handshaking = false;
165: }
166: if (result.getStatus() == Status.CLOSED) {
167: this .status = CLOSED;
168: }
169: break;
170: case NEED_TASK:
171: Runnable r = this .sslEngine.getDelegatedTask();
172: r.run();
173: break;
174: case NOT_HANDSHAKING:
175: handshaking = false;
176: break;
177: }
178: }
179:
180: // The SSLEngine has just finished handshaking. This value is only generated by a call
181: // to SSLEngine.wrap()/unwrap() when that call finishes a handshake.
182: // It is never generated by SSLEngine.getHandshakeStatus().
183: if (result != null
184: && result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
185: if (this .handler != null) {
186: this .handler.verify(this .session.getRemoteAddress(),
187: this .sslEngine.getSession());
188: }
189: }
190: }
191:
192: private void updateEventMask() {
193: // Need to toggle the event mask for this channel?
194: int oldMask = this .session.getEventMask();
195: int newMask = oldMask;
196: switch (this .sslEngine.getHandshakeStatus()) {
197: case NEED_WRAP:
198: newMask = EventMask.READ_WRITE;
199: break;
200: case NEED_UNWRAP:
201: newMask = EventMask.READ;
202: break;
203: case NOT_HANDSHAKING:
204: newMask = this .appEventMask;
205: break;
206: }
207:
208: // Do we have encrypted data ready to be sent?
209: if (this .outEncrypted.position() > 0) {
210: newMask = newMask | EventMask.WRITE;
211: }
212:
213: // Update the mask if necessary
214: if (oldMask != newMask) {
215: this .session.setEventMask(newMask);
216: }
217: }
218:
219: private int sendEncryptedData() throws IOException {
220: this .outEncrypted.flip();
221: int bytesWritten = this .session.channel().write(
222: this .outEncrypted);
223: this .outEncrypted.compact();
224:
225: if (this .sslEngine.isInboundDone()
226: && this .sslEngine.isOutboundDone()) {
227: this .session.close();
228: }
229: return bytesWritten;
230: }
231:
232: private int receiveEncryptedData() throws IOException {
233: return this .session.channel().read(this .inEncrypted);
234: }
235:
236: private boolean decryptData() throws SSLException {
237: boolean decrypted = false;
238: if (this .inEncrypted.position() > 0) {
239: this .inEncrypted.flip();
240: SSLEngineResult result = this .sslEngine.unwrap(
241: this .inEncrypted, this .inPlain);
242: this .inEncrypted.compact();
243: if (result.getStatus() == Status.CLOSED) {
244: this .status = CLOSED;
245: }
246: if (result.getStatus() == Status.OK) {
247: decrypted = true;
248: }
249: }
250: return decrypted;
251: }
252:
253: public synchronized boolean isAppInputReady() throws IOException {
254: int bytesRead = receiveEncryptedData();
255: if (bytesRead == -1) {
256: this .status = CLOSED;
257: }
258: doHandshake();
259: decryptData();
260: // Some decrypted data is available or at the end of stream
261: return this .inPlain.position() > 0 || this .status != ACTIVE;
262: }
263:
264: public synchronized boolean isAppOutputReady() throws IOException {
265: return this .status == ACTIVE
266: && this .sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
267: }
268:
269: public synchronized void inboundTransport() throws IOException {
270: if (this .status == CLOSED) {
271: this .session.close();
272: } else {
273: updateEventMask();
274: }
275: }
276:
277: public synchronized void outboundTransport() throws IOException {
278: sendEncryptedData();
279: doHandshake();
280: updateEventMask();
281: }
282:
283: private synchronized int writePlain(final ByteBuffer src)
284: throws SSLException {
285: if (src == null) {
286: throw new IllegalArgumentException(
287: "Byte buffer may not be null");
288: }
289: if (this .status != ACTIVE) {
290: return -1;
291: }
292: if (this .outPlain.position() > 0) {
293: this .outPlain.flip();
294: this .sslEngine.wrap(this .outPlain, this .outEncrypted);
295: this .outPlain.compact();
296: }
297: if (this .outPlain.position() == 0) {
298: SSLEngineResult result = this .sslEngine.wrap(src,
299: this .outEncrypted);
300: if (result.getStatus() == Status.CLOSED) {
301: this .status = CLOSED;
302: }
303: return result.bytesConsumed();
304: } else {
305: return 0;
306: }
307: }
308:
309: private synchronized int readPlain(final ByteBuffer dst)
310: throws SSLException {
311: if (dst == null) {
312: throw new IllegalArgumentException(
313: "Byte buffer may not be null");
314: }
315: if (this .inPlain.position() > 0) {
316: this .inPlain.flip();
317: int n = Math.min(this .inPlain.remaining(), dst.remaining());
318: for (int i = 0; i < n; i++) {
319: dst.put(this .inPlain.get());
320: }
321: this .inPlain.compact();
322: return n;
323: } else {
324: if (this .status == ACTIVE) {
325: return 0;
326: } else {
327: return -1;
328: }
329: }
330: }
331:
332: public void close() {
333: if (this .status != ACTIVE) {
334: return;
335: }
336: this .status = CLOSING;
337: synchronized (this ) {
338: this .sslEngine.closeOutbound();
339: updateEventMask();
340: }
341: }
342:
343: public void shutdown() {
344: this .status = CLOSED;
345: this .session.shutdown();
346: }
347:
348: public int getStatus() {
349: return this .status;
350: }
351:
352: public boolean isClosed() {
353: return this .status != ACTIVE;
354: }
355:
356: public synchronized boolean isInboundDone() {
357: return this .sslEngine.isInboundDone();
358: }
359:
360: public synchronized boolean isOutboundDone() {
361: return this .sslEngine.isOutboundDone();
362: }
363:
364: public ByteChannel channel() {
365: return this .channel;
366: }
367:
368: public SocketAddress getLocalAddress() {
369: return this .session.getLocalAddress();
370: }
371:
372: public SocketAddress getRemoteAddress() {
373: return this .session.getRemoteAddress();
374: }
375:
376: public synchronized int getEventMask() {
377: return this .appEventMask;
378: }
379:
380: public synchronized void setEventMask(int ops) {
381: this .appEventMask = ops;
382: updateEventMask();
383: }
384:
385: public synchronized void setEvent(int op) {
386: this .appEventMask = this .appEventMask | op;
387: updateEventMask();
388: }
389:
390: public synchronized void clearEvent(int op) {
391: this .appEventMask = this .appEventMask & ~op;
392: updateEventMask();
393: }
394:
395: public int getSocketTimeout() {
396: return this .session.getSocketTimeout();
397: }
398:
399: public void setSocketTimeout(int timeout) {
400: this .session.setSocketTimeout(timeout);
401: }
402:
403: public synchronized boolean hasBufferedInput() {
404: return this .appBufferStatus.hasBufferedInput()
405: || this .inEncrypted.position() > 0
406: || this .inPlain.position() > 0;
407: }
408:
409: public synchronized boolean hasBufferedOutput() {
410: return this .appBufferStatus.hasBufferedOutput()
411: || this .outEncrypted.position() > 0
412: || this .outPlain.position() > 0;
413: }
414:
415: public synchronized void setBufferStatus(
416: final SessionBufferStatus status) {
417: this .appBufferStatus = status;
418: }
419:
420: public Object getAttribute(final String name) {
421: return this .session.getAttribute(name);
422: }
423:
424: public Object removeAttribute(final String name) {
425: return this .session.removeAttribute(name);
426: }
427:
428: public void setAttribute(final String name, final Object obj) {
429: this .session.setAttribute(name, obj);
430: }
431:
432: @Override
433: public String toString() {
434: StringBuffer buffer = new StringBuffer();
435: buffer.append(this .session);
436: buffer.append("[SSL handshake status: ");
437: buffer.append(this .sslEngine.getHandshakeStatus());
438: buffer.append("][");
439: buffer.append(this .inEncrypted.position());
440: buffer.append("][");
441: buffer.append(this .inPlain.position());
442: buffer.append("][");
443: buffer.append(this .outEncrypted.position());
444: buffer.append("][");
445: buffer.append(this .outPlain.position());
446: buffer.append("]");
447: return buffer.toString();
448: }
449:
450: private class InternalByteChannel implements ByteChannel {
451:
452: public int write(final ByteBuffer src) throws IOException {
453: return SSLIOSession.this .writePlain(src);
454: }
455:
456: public int read(final ByteBuffer dst) throws IOException {
457: return SSLIOSession.this .readPlain(dst);
458: }
459:
460: public void close() throws IOException {
461: SSLIOSession.this .close();
462: }
463:
464: public boolean isOpen() {
465: return !SSLIOSession.this.isClosed();
466: }
467:
468: }
469:
470: }
|