/* * SecureTransport.java February 2007 * * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.simpleframework.transport; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Map; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.Status; import org.simpleframework.transport.trace.Trace; /** * The <code>SecureTransport</code> object provides an implementation of a * transport used to send and receive data over SSL. Data read from this * transport is decrypted using an <code>SSLEngine</code>. Also, all data is * written is encrypted with the same engine. This ensures that data can be send * and received in a transparent way. * * @author Niall Gallagher */ class SecureTransport implements Transport { /** * This is the transport used to send data over the socket. */ private Transport transport; /** * This buffer is used to output the data for the SSL sent. */ private ByteBuffer output; /** * This is the internal buffer used to exchange the SSL data. */ private ByteBuffer input; /** * This is the internal buffer used to exchange the SSL data. */ private ByteBuffer swap; /** * This is the SSL engine used to encrypt and decrypt data. */ private SSLEngine engine; /** * This is the trace that is used to monitor socket activity. */ private Trace trace; /** * This is used to determine if the transport was closed. */ private boolean closed; /** * This is used to determine if the end of stream was reached. */ private boolean finished; /** * Constructor for the <code>SecureTransport</code> object. This is used to * create a transport for sending and receiving data over SSL. This must be * created with a pipeline that has already performed the SSL handshake and * is read to used. * * @param transport * this is the transport to delegate operations to * @param input * this is the input buffer used to read the data * @param swap * this is the swap buffer to be used for reading */ public SecureTransport(Transport transport, ByteBuffer input, ByteBuffer swap) { this(transport, input, swap, 20480); } /** * Constructor for the <code>SecureTransport</code> object. This is used to * create a transport for sending and receiving data over SSL. This must be * created with a pipeline that has already performed the SSL handshake and * is read to used. * * @param transport * this is the transport to delegate operations to * @param input * this is the input buffer used to read the data * @param swap * this is the swap buffer to be used for reading * @param size * this is the size of the buffers to be allocated */ public SecureTransport(Transport transport, ByteBuffer input, ByteBuffer swap, int size) { this.output = ByteBuffer.allocate(size); this.engine = transport.getEngine(); this.trace = transport.getTrace(); this.transport = transport; this.input = input; this.swap = swap; } /** * This is used to acquire the trace object that is associated with the * socket. A trace object is used to collection details on what operations * are being performed on the socket. For instance it may contain * information relating to I/O events or more application specific events * such as errors. * * @return this returns the trace associated with this socket */ @Override public Trace getTrace() { return this.trace; } /** * This is used to acquire the SSL engine used for HTTPS. If the pipeline is * connected to an SSL transport this returns an SSL engine which can be * used to establish the secure connection and send and receive content over * that connection. If this is null then the pipeline represents a normal * transport. * * @return the SSL engine used to establish a secure transport */ @Override public SSLEngine getEngine() { return this.engine; } /** * This method is used to get the <code>Map</code> of attributes by this * pipeline. The attributes map is used to maintain details about the * connection. Information such as security credentials to client details * can be placed within the attribute map. * * @return this returns the map of attributes for this pipeline */ @Override public Map getAttributes() { return this.transport.getAttributes(); } /** * This method is used to acquire the <code>SocketChannel</code> for the * connection. This allows the server to acquire the input and output * streams with which to communicate. It can also be used to configure the * connection and perform various network operations that could otherwise * not be performed. * * @return this returns the socket used by this HTTP pipeline */ @Override public SocketChannel getChannel() { return this.transport.getChannel(); } /** * This is used to perform a non-blocking read on the transport. If there * are no bytes available on the input buffers then this method will return * zero and the buffer will remain the same. If there is data and the buffer * can be filled then this will return the number of bytes read. Finally if * the socket is closed this will return a -1 value. * * @param buffer * this is the buffer to append the bytes to * * @return this returns the number of bytes that have been read */ @Override public int read(ByteBuffer buffer) throws IOException { if (this.closed) throw new TransportException("Transport is closed"); if (this.finished) return -1; int count = this.fill(buffer); if (count <= 0) return this.process(buffer); return count; } /** * This is used to perform a non-blocking read on the transport. If there * are no bytes available on the input buffers then this method will return * zero and the buffer will remain the same. If there is data and the buffer * can be filled then this will return the number of bytes read. * * @param buffer * this is the buffer to append the bytes to * * @return this returns the number of bytes that have been read */ private int process(ByteBuffer buffer) throws IOException { int size = this.swap.position(); if (size >= 0) { this.swap.compact(); } int space = this.swap.remaining(); if (space > 0) { size = this.transport.read(this.swap); if (size < 0) { this.finished = true; } } if ((size > 0) || (space > 0)) { this.swap.flip(); this.receive(); } return this.fill(buffer); } /** * This is used to fill the provided buffer with data that has been read * from the secure socket channel. This enables reading of the decrypted * data in chunks that are smaller than the size of the input buffer used to * contain the plain text data. * * @param buffer * this is the buffer to append the bytes to * * @return this returns the number of bytes that have been read */ private int fill(ByteBuffer buffer) throws IOException { int space = buffer.remaining(); int count = this.input.position(); if (count > 0) { if (count > space) { count = space; } } return this.fill(buffer, count); } /** * This is used to fill the provided buffer with data that has been read * from the secure socket channel. This enables reading of the decrypted * data in chunks that are smaller than the size of the input buffer used to * contain the plain text data. * * @param buffer * this is the buffer to append the bytes to * @param count * this is the number of bytes that are to be read * * @return this returns the number of bytes that have been read */ private int fill(ByteBuffer buffer, int count) throws IOException { this.input.flip(); if (count > 0) { count = this.append(buffer, count); } this.input.compact(); return count; } /** * This will append bytes within the transport to the given buffer. Once * invoked the buffer will contain the transport bytes, which will have been * drained from the buffer. This effectively moves the bytes in the buffer * to the end of the packet instance. * * @param buffer * this is the buffer containing the bytes * @param count * this is the number of bytes that should be used * * @return returns the number of bytes that have been moved */ private int append(ByteBuffer buffer, int count) throws IOException { ByteBuffer segment = this.input.slice(); if (this.closed) throw new TransportException("Transport is closed"); int mark = this.input.position(); int size = mark + count; if (count > 0) { this.input.position(size); segment.limit(count); buffer.put(segment); } return count; } /** * This is used to perform a non-blocking read on the transport. If there * are no bytes available on the input buffers then this method will return * zero and the buffer will remain the same. If there is data and the buffer * can be filled then this will return the number of bytes read. Finally if * the socket is closed this will return a -1 value. */ private void receive() throws IOException { int count = this.swap.remaining(); while (count > 0) { SSLEngineResult result = this.engine.unwrap(this.swap, this.input); Status status = result.getStatus(); switch (status) { case BUFFER_OVERFLOW: case BUFFER_UNDERFLOW: return; case CLOSED: throw new TransportException("Transport error " + result); } count = this.swap.remaining(); if (count <= 0) { break; } } } /** * This method is used to deliver the provided buffer of bytes to the * underlying transport. Depending on the connection type the array may be * encoded for SSL transport or send directly. Any implementation may choose * to buffer the bytes for performance. * * @param buffer * this is the array of bytes to send to the client */ @Override public void write(ByteBuffer buffer) throws IOException { if (this.closed) throw new TransportException("Transport is closed"); int capacity = this.output.capacity(); int ready = buffer.remaining(); int length = ready; while (ready > 0) { int size = Math.min(ready, capacity / 2); int mark = buffer.position(); if ((length * 2) > capacity) { buffer.limit(mark + size); } this.send(buffer); this.output.clear(); ready -= size; } } /** * This method is used to deliver the provided buffer of bytes to the * underlying transport. Depending on the connection type the array may be * encoded for SSL transport or send directly. Any implementation may choose * to buffer the bytes for performance. * * @param buffer * this is the array of bytes to send to the client */ private void send(ByteBuffer buffer) throws IOException { SSLEngineResult result = this.engine.wrap(buffer, this.output); Status status = result.getStatus(); switch (status) { case BUFFER_OVERFLOW: case BUFFER_UNDERFLOW: case CLOSED: throw new TransportException("Transport error " + status); default: this.output.flip(); } this.transport.write(this.output); } /** * This method is used to flush the contents of the buffer to the client. * This method will block until such time as all of the data has been sent * to the client. If at any point there is an error sending the content an * exception is thrown. */ @Override public void flush() throws IOException { if (this.closed) throw new TransportException("Transport is closed"); this.transport.flush(); } /** * This is used to close the sender and the underlying transport. If a close * is performed on the sender then no more bytes can be read from or written * to the transport and the client will received a connection close on their * side. */ @Override public void close() throws IOException { if (!this.closed) { this.transport.close(); this.closed = true; } } }