summaryrefslogtreecommitdiffstats
path: root/simple/simple-transport/src/main/java/org/simpleframework/transport/Handshake.java
diff options
context:
space:
mode:
Diffstat (limited to 'simple/simple-transport/src/main/java/org/simpleframework/transport/Handshake.java')
-rw-r--r--simple/simple-transport/src/main/java/org/simpleframework/transport/Handshake.java652
1 files changed, 652 insertions, 0 deletions
diff --git a/simple/simple-transport/src/main/java/org/simpleframework/transport/Handshake.java b/simple/simple-transport/src/main/java/org/simpleframework/transport/Handshake.java
new file mode 100644
index 0000000..2a4b9a2
--- /dev/null
+++ b/simple/simple-transport/src/main/java/org/simpleframework/transport/Handshake.java
@@ -0,0 +1,652 @@
+/*
+ * Handshake.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 static java.nio.channels.SelectionKey.OP_READ;
+import static java.nio.channels.SelectionKey.OP_WRITE;
+import static org.simpleframework.transport.PhaseType.COMMIT;
+import static org.simpleframework.transport.PhaseType.CONSUME;
+import static org.simpleframework.transport.PhaseType.PRODUCE;
+import static org.simpleframework.transport.TransportEvent.ERROR;
+import static org.simpleframework.transport.TransportEvent.HANDSHAKE_BEGIN;
+import static org.simpleframework.transport.TransportEvent.HANDSHAKE_DONE;
+import static org.simpleframework.transport.TransportEvent.HANDSHAKE_FAILED;
+import static org.simpleframework.transport.TransportEvent.READ;
+import static org.simpleframework.transport.TransportEvent.WRITE;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.Future;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+
+import org.simpleframework.transport.reactor.Reactor;
+import org.simpleframework.transport.trace.Trace;
+
+/**
+ * The <code>Handshake</code> object is used to perform secure SSL
+ * negotiations on a pipeline or <code>Transport</code>. This can
+ * be used to perform an SSL handshake. To perform the negotiation
+ * this uses an SSL engine provided with the transport to direct
+ * the conversation. The SSL engine tells the negotiation what is
+ * expected next, whether this is a response to the client or a
+ * message from it. During the negotiation this may need to wait
+ * for either a write ready event or a read ready event. Event
+ * notification is done using the processor provided.
+ *
+ * @author Niall Gallagher
+ *
+ * @see org.simpleframework.transport.TransportProcessor
+ */
+class Handshake implements Negotiation {
+
+ /**
+ * This is the processor used to process the secure transport.
+ */
+ private final TransportProcessor processor;
+
+ /**
+ * This is the certificate associated with this negotiation.
+ */
+ private final NegotiationState state;
+
+ /**
+ * This is the socket channel used to read and write data to.
+ */
+ private final SocketChannel channel;
+
+ /**
+ * This is the transport dispatched when the negotiation ends.
+ */
+ private final Transport transport;
+
+ /**
+ * This is the reactor used to register for I/O notifications.
+ */
+ private final Reactor reactor;
+
+ /**
+ * This is the output buffer used to generate data to.
+ */
+ private final ByteBuffer output;
+
+ /**
+ * This is the input buffer used to read data from the socket.
+ */
+ private final ByteBuffer input;
+
+ /**
+ * This is an empty byte buffer used to generate a response.
+ */
+ private final ByteBuffer empty;
+
+ /**
+ * This is the SSL engine used to direct the conversation.
+ */
+ private final SSLEngine engine;
+
+ /**
+ * This is the trace that is used to monitor handshake events.
+ */
+ private final Trace trace;
+
+ /**
+ * This determines if the handshake is from the client side.
+ */
+ private final boolean client;
+
+ /**
+ * Constructor for the <code>Handshake</code> object. This is
+ * used to create an operation capable of performing negotiations
+ * for SSL connections. Typically this is used to perform request
+ * response negotiations, such as a handshake or termination.
+ *
+ * @param processor the processor used to dispatch the transport
+ * @param transport the transport to perform the negotiation for
+ * @param reactor this is the reactor used for I/O notifications
+ */
+ public Handshake(TransportProcessor processor, Transport transport, Reactor reactor) {
+ this(processor, transport, reactor, 20480);
+ }
+
+ /**
+ * Constructor for the <code>Handshake</code> object. This is
+ * used to create an operation capable of performing negotiations
+ * for SSL connections. Typically this is used to perform request
+ * response negotiations, such as a handshake or termination.
+ *
+ * @param transport the transport to perform the negotiation for
+ * @param processor the processor used to dispatch the transport
+ * @param reactor this is the reactor used for I/O notifications
+ * @param size the size of the buffers used for the negotiation
+ */
+ public Handshake(TransportProcessor processor, Transport transport, Reactor reactor, int size) {
+ this(processor, transport, reactor, size, false);
+ }
+
+ /**
+ * Constructor for the <code>Handshake</code> object. This is
+ * used to create an operation capable of performing negotiations
+ * for SSL connections. Typically this is used to perform request
+ * response negotiations, such as a handshake or termination.
+ *
+ * @param transport the transport to perform the negotiation for
+ * @param processor the processor used to dispatch the transport
+ * @param reactor this is the reactor used for I/O notifications
+ * @param client determines the side of the SSL handshake
+ */
+ public Handshake(TransportProcessor processor, Transport transport, Reactor reactor, boolean client) {
+ this(processor, transport, reactor, 20480, client);
+ }
+
+ /**
+ * Constructor for the <code>Handshake</code> object. This is
+ * used to create an operation capable of performing negotiations
+ * for SSL connections. Typically this is used to perform request
+ * response negotiations, such as a handshake or termination.
+ *
+ * @param transport the transport to perform the negotiation for
+ * @param processor the processor used to dispatch the transport
+ * @param reactor this is the reactor used for I/O notifications
+ * @param size the size of the buffers used for the negotiation
+ * @param client determines the side of the SSL handshake
+ */
+ public Handshake(TransportProcessor processor, Transport transport, Reactor reactor, int size, boolean client) {
+ this.state = new NegotiationState(this, transport);
+ this.output = ByteBuffer.allocate(size);
+ this.input = ByteBuffer.allocate(size);
+ this.channel = transport.getChannel();
+ this.engine = transport.getEngine();
+ this.trace = transport.getTrace();
+ this.empty = ByteBuffer.allocate(0);
+ this.processor = processor;
+ this.transport = transport;
+ this.reactor = reactor;
+ this.client = client;
+ }
+
+ /**
+ * This is used to acquire the trace object that is associated
+ * with the operation. A trace object is used to collection details
+ * on what operations are being performed. For instance it may
+ * contain information relating to I/O events or errors.
+ *
+ * @return this returns the trace associated with this operation
+ */
+ public Trace getTrace() {
+ return trace;
+ }
+
+ /**
+ * This returns the socket channel for the connected pipeline. It
+ * is this channel that is used to determine if there are bytes
+ * that can be read. When closed this is no longer selectable.
+ *
+ * @return this returns the connected channel for the pipeline
+ */
+ public SelectableChannel getChannel() {
+ return channel;
+ }
+
+ /**
+ * This is used to start the negotiation. Once started this will
+ * send a message to the other side, once sent the negotiation
+ * reads the response. However if the response is not yet ready
+ * this will schedule the negotiation for a selectable operation
+ * ensuring that it can resume execution when ready.
+ */
+ public void run() {
+ if(engine != null) {
+ trace.trace(HANDSHAKE_BEGIN);
+ engine.setUseClientMode(client);
+ input.flip();
+ }
+ begin();
+ }
+
+ /**
+ * This is used to terminate the negotiation. This is excecuted
+ * when the negotiation times out. When the negotiation expires it
+ * is rejected by the processor and must be canceled. Canceling
+ * is basically termination of the connection to free resources.
+ */
+ public void cancel() {
+ try {
+ terminate();
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ }
+ }
+
+ /**
+ * This is used to start the negotation. Once started this will
+ * send a message to the other side, once sent the negotiation
+ * reads the response. However if the response is not yet ready
+ * this will schedule the negotiation for a selectable operation
+ * ensuring that it can resume execution when ready.
+ */
+ private void begin() {
+ try {
+ resume();
+ } catch(Exception cause) {
+ trace.trace(ERROR, cause);
+ cancel();
+ }
+ }
+
+ /**
+ * This is the main point of execution within the negotiation. It
+ * is where the negotiation is performed. Negotiations are done
+ * by performing a request response flow, governed by the SSL
+ * engine associated with the pipeline. Typically the client is
+ * the one to initiate the handshake and the server initiates the
+ * termination sequence. This may be executed several times
+ * depending on whether reading or writing blocks.
+ */
+ public void resume() throws IOException {
+ Runnable task = process();
+
+ if(task != null) {
+ task.run();
+ }
+ }
+
+ /**
+ * This is the main point of execution within the negotiation. It
+ * is where the negotiation is performed. Negotiations are done
+ * by performing a request response flow, governed by the SSL
+ * engine associated with the transport. Typically the client is
+ * the one to initiate the handshake and the server initiates the
+ * termination sequence. This may be executed several times
+ * depending on whether reading or writing blocks.
+ *
+ * @return this returns a task used to execute the next phase
+ */
+ private Runnable process() throws IOException {
+ PhaseType require = exchange();
+
+ if(require == CONSUME) {
+ return new Consumer(this, reactor, trace);
+ }
+ if(require == PRODUCE) {
+ return new Producer(this, reactor, trace);
+ }
+ return new Committer(this, reactor, trace);
+ }
+
+ /**
+ * This is the main point of execution within the negotiation. It
+ * is where the negotiation is performed. Negotiations are done
+ * by performing a request response flow, governed by the SSL
+ * engine associated with the transport. Typically the client is
+ * the one to initiate the handshake and the server initiates the
+ * termination sequence. This may be executed several times
+ * depending on whether reading or writing blocks.
+ *
+ * @return this returns what is expected next in the negotiation
+ */
+ private PhaseType exchange() throws IOException {
+ HandshakeStatus status = engine.getHandshakeStatus();
+
+ switch(status){
+ case NEED_WRAP:
+ return write();
+ case NOT_HANDSHAKING:
+ case NEED_UNWRAP:
+ return read();
+ }
+ return COMMIT;
+ }
+
+ /**
+ * This is used to perform the read part of the negotiation. The
+ * read part is where the other side sends information where it
+ * is consumed and is used to determine what action to take.
+ * Typically it is the SSL engine that determines what action is
+ * to be taken depending on the data send from the other side.
+ *
+ * @return the next action that should be taken by the handshake
+ */
+ private PhaseType read() throws IOException {
+ return read(5);
+ }
+
+ /**
+ * This is used to perform the read part of the negotiation. The
+ * read part is where the other side sends information where it
+ * is consumed and is used to determine what action to take.
+ * Typically it is the SSL engine that determines what action is
+ * to be taken depending on the data send from the other side.
+ *
+ * @param count this is the number of times a read can repeat
+ *
+ * @return the next action that should be taken by the handshake
+ */
+ private PhaseType read(int count) throws IOException {
+ while(count > 0) {
+ SSLEngineResult result = engine.unwrap(input, output);
+ HandshakeStatus status = result.getHandshakeStatus();
+
+ switch(status) {
+ case NOT_HANDSHAKING:
+ return COMMIT;
+ case NEED_WRAP:
+ return PRODUCE;
+ case FINISHED:
+ case NEED_UNWRAP:
+ return read(count-1);
+ case NEED_TASK:
+ execute();
+ }
+ }
+ return CONSUME;
+ }
+
+ /**
+ * This is used to perform the write part of the negotiation. The
+ * read part is where the this sends information to the other side
+ * and the other side interprets the data and determines what action
+ * to take. After a write the negotiation typically completes or
+ * waits for the next response from the other side.
+ *
+ * @return the next action that should be taken by the handshake
+ */
+ private PhaseType write() throws IOException {
+ return write(5);
+ }
+
+ /**
+ * This is used to perform the write part of the negotiation. The
+ * read part is where the this sends information to the other side
+ * and the other side interprets the data and determines what action
+ * to take. After a write the negotiation typically completes or
+ * waits for the next response from the other side.
+ *
+ * @param count this is the number of times a read can repeat
+ *
+ * @return the next action that should be taken by the handshake
+ */
+ private PhaseType write(int count) throws IOException {
+ while(count > 0) {
+ SSLEngineResult result = engine.wrap(empty, output);
+ HandshakeStatus status = result.getHandshakeStatus();
+
+ switch(status) {
+ case NOT_HANDSHAKING:
+ case FINISHED:
+ case NEED_UNWRAP:
+ return PRODUCE;
+ case NEED_WRAP:
+ return write(count-1);
+ case NEED_TASK:
+ execute();
+ }
+ }
+ return PRODUCE;
+ }
+
+ /**
+ * This is used to execute the delegated tasks. These tasks are
+ * used to digest the information received from the client in
+ * order to generate a response. This may need to execute several
+ * tasks from the associated SSL engine.
+ */
+ private void execute() throws IOException {
+ while(true) {
+ Runnable task = engine.getDelegatedTask();
+
+ if(task == null) {
+ break;
+ }
+ task.run();
+ }
+ }
+
+ /**
+ * This is used to receive data from the client. If at any
+ * point during the negotiation a message is required that
+ * can not be read immediately this is used to asynchronously
+ * read the data when a select operation is signalled.
+ *
+ * @return this returns true when the message has been read
+ */
+ public boolean receive() throws IOException {
+ int count = input.capacity();
+
+ if(count > 0) {
+ input.compact();
+ }
+ int size = channel.read(input);
+
+ if(trace != null) {
+ trace.trace(READ, size);
+ }
+ if(size < 0) {
+ throw new TransportException("Client closed connection");
+ }
+ if(count > 0) {
+ input.flip();
+ }
+ return size > 0;
+ }
+
+ /**
+ * Here we attempt to send all data within the output buffer. If
+ * all of the data is delivered to the other side then this will
+ * return true. If however there is content yet to be sent to
+ * the other side then this returns false, telling the negotiation
+ * that in order to resume it must attempt to send the content
+ * again after a write ready operation on the underlying socket.
+ *
+ * @return this returns true if all of the content is delivered
+ */
+ public boolean send() throws IOException {
+ int require = output.position();
+ int count = 0;
+
+ if(require > 0) {
+ output.flip();
+ }
+ while(count < require) {
+ int size = channel.write(output);
+
+ if(trace != null) {
+ trace.trace(WRITE, size);
+ }
+ if(size <= 0) {
+ break;
+ }
+ count += size;
+ }
+ if(require > 0) {
+ output.compact();
+ }
+ return count == require;
+ }
+
+ /**
+ * This method is invoked when the negotiation is done and the
+ * next phase of the connection is to take place. This will
+ * be invoked when the SSL handshake has completed and the new
+ * secure transport is to be handed to the processor.
+ */
+ private void dispatch() throws IOException {
+ Transport secure = new SecureTransport(transport, state, output, input);
+
+ if(processor != null) {
+ trace.trace(HANDSHAKE_DONE);
+ processor.process(secure);
+ }
+ }
+
+ /**
+ * This method is used to terminate the handshake. Termination
+ * typically occurs when there has been some error in the handshake
+ * or when there is a timeout on some event, such as waiting for
+ * for a read or write operation to occur. As a result the TCP
+ * channel is closed and any challenge future is cancelled.
+ */
+ private void terminate() throws IOException {
+ Future<Certificate> future = state.getFuture();
+
+ trace.trace(HANDSHAKE_FAILED);
+ transport.close();
+ future.cancel(true);
+ }
+
+ /**
+ * This is used to execute the completion task after a challenge
+ * for the clients X509 certificate. Execution of the completion
+ * task in this way allows any challanger to be notified that
+ * the handshake has complete.
+ */
+ private void complete() throws IOException {
+ Runnable task = state.getFuture();
+
+ if(task != null) {
+ task.run();
+ }
+ }
+
+ /**
+ * This method is invoked when the negotiation is done and the
+ * next phase of the connection is to take place. If a certificate
+ * challenge was issued then the completion task is executed, if
+ * this was the handshake for the initial connection a transport
+ * is created and handed to the processor.
+ */
+ public void commit() throws IOException {
+ if(!state.isChallenge()) {
+ dispatch();
+ } else {
+ complete();
+ }
+ }
+
+ /**
+ * The <code>Committer</code> task is used to transfer the transport
+ * created to the processor. This is executed when the SSL
+ * handshake is completed. It allows the transporter to use the
+ * newly created transport to read and write in plain text and
+ * to have the SSL transport encrypt and decrypt transparently.
+ */
+ private class Committer extends Phase {
+
+ /**
+ * Constructor for the <code>Committer</code> task. This is used to
+ * pass the transport object object to the processor when the
+ * SSL handshake has completed.
+ *
+ * @param state this is the underlying negotiation to use
+ * @param reactor this is the reactor used for I/O notifications
+ * @param trace the trace that is used to monitor the handshake
+ */
+ public Committer(Negotiation state, Reactor reactor, Trace trace) {
+ super(state, reactor, trace, OP_READ);
+ }
+
+ /**
+ * This is used to execute the task. It is up to the specific
+ * task implementation to decide what to do when executed. If
+ * the task needs to read or write data then it can attempt
+ * to perform the read or write, if it incomplete the it can
+ * be scheduled for execution with the reactor.
+ */
+ @Override
+ public void execute() throws IOException{
+ state.commit();
+ }
+ }
+
+ /**
+ * The <code>Consumer</code> task is used to schedule the negotiation
+ * for a read operation. This allows the negotiation to receive any
+ * messages generated by the client asynchronously. Once this has
+ * completed then it will resume the negotiation.
+ */
+ private class Consumer extends Phase {
+
+ /**
+ * Constructor for the <code>Consumer</code> task. This is used
+ * to create a task which will schedule a read operation for
+ * the negotiation. When the operation completes this will
+ * resume the negotiation.
+ *
+ * @param state this is the negotiation object that is used
+ * @param reactor this is the reactor used for I/O notifications
+ * @param trace the trace that is used to monitor the handshake
+ */
+ public Consumer(Negotiation state, Reactor reactor, Trace trace) {
+ super(state, reactor, trace, OP_READ);
+ }
+
+ /**
+ * This method is used to determine if the task is ready. This
+ * is executed when the select operation is signalled. When this
+ * is true the the task completes. If not then this will
+ * schedule the task again for the specified select operation.
+ *
+ * @return this returns true when the task has completed
+ */
+ @Override
+ protected boolean ready() throws IOException {
+ return state.receive();
+ }
+ }
+
+ /**
+ * The <code>Producer</code> is used to schedule the negotiation
+ * for a write operation. This allows the negotiation to send any
+ * messages generated during the negotiation asynchronously. Once
+ * this has completed then it will resume the negotiation.
+ */
+ private class Producer extends Phase {
+
+ /**
+ * Constructor for the <code>Producer</code> task. This is used
+ * to create a task which will schedule a write operation for
+ * the negotiation. When the operation completes this will
+ * resume the negotiation.
+ *
+ * @param state this is the negotiation object that is used
+ * @param reactor this is the reactor used for I/O notifications
+ * @param trace the trace that is used to monitor the handshake
+ */
+ public Producer(Negotiation state, Reactor reactor, Trace trace) {
+ super(state, reactor, trace, OP_WRITE);
+ }
+
+ /**
+ * This method is used to determine if the task is ready. This
+ * is executed when the select operation is signalled. When this
+ * is true the the task completes. If not then this will
+ * schedule the task again for the specified select operation.
+ *
+ * @return this returns true when the task has completed
+ */
+ @Override
+ protected boolean ready() throws IOException {
+ return state.send();
+ }
+ }
+}