/* * 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.Status.CLIENT; import static org.simpleframework.transport.Status.DONE; import static org.simpleframework.transport.Status.SERVER; 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.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 javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; 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 pipeline 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 * negotiator provided. * * @author Niall Gallagher * * @see org.simpleframework.transport.Negotiator */ class Handshake implements Negotiation { /** * This is the negotiator used to process the secure transport. */ private final Negotiator negotiator; /** * 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 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; /** * Constructor for the <code>Negotiation</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 negotiator * the negotiator used to check socket events * @param transport * the transport to perform the negotiation for */ public Handshake(Transport transport, Negotiator negotiator) { this(transport, negotiator, 20480); } /** * Constructor for the <code>Negotiation</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 negotiator * the negotiator used to check socket events * @param transport * the transport to perform the negotiation for * @param size * the size of the buffers used for the negotiation */ public Handshake(Transport transport, Negotiator negotiator, int size) { 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.negotiator = negotiator; this.transport = transport; } /** * 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 */ @Override public SelectableChannel getChannel() { return this.channel; } /** * This is used to start the negotiation. Once started this will send a * message to the client, 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. */ @Override public void run() { if (this.engine != null) { this.trace.trace(HANDSHAKE_BEGIN); this.engine.setUseClientMode(false); this.input.flip(); } this.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 * negotiator and must be canceled. Canceling is basically termination of * the connection to free resources. */ @Override public void cancel() { try { this.transport.close(); } catch (Exception cause) { this.trace.trace(ERROR, cause); } } /** * This is used to start the negotation. Once started this will send a * message to the client, 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 { this.resume(); } catch (Exception cause) { this.trace.trace(ERROR, cause); } } /** * 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. */ @Override public void resume() throws IOException { Runnable task = this.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 * 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. * * @return this returns a task used to execute the next phase */ private Runnable process() throws IOException { Status require = this.exchange(); if (require == CLIENT) return new Client(this, this.trace); if (require == SERVER) return new Server(this, this.trace); return new Done(this, this.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 * 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. * * @return this returns what is expected next in the negotiation */ private Status exchange() throws IOException { HandshakeStatus status = this.engine.getHandshakeStatus(); switch (status) { case NEED_WRAP: return this.write(); case NOT_HANDSHAKING: case NEED_UNWRAP: return this.read(); } return DONE; } /** * This is used to perform the read part of the negotiation. The read part * is where the client sends information to the server and the server * consumes the data and determines what action to take. Typically it is the * SSL engine that determines what action is to be taken depending on the * client data. * * @return the next action that should be taken by the handshake */ private Status read() throws IOException { return this.read(5); } /** * This is used to perform the read part of the negotiation. The read part * is where the client sends information to the server and the server * consumes the data and determines what action to take. Typically it is the * SSL engine that determines what action is to be taken depending on the * client data. * * @param count * this is the number of times a read can repeat * * @return the next action that should be taken by the handshake */ private Status read(int count) throws IOException { while (count > 0) { SSLEngineResult result = this.engine .unwrap(this.input, this.output); HandshakeStatus status = result.getHandshakeStatus(); switch (status) { case NOT_HANDSHAKING: return DONE; case NEED_WRAP: return SERVER; case FINISHED: case NEED_UNWRAP: return this.read(count - 1); case NEED_TASK: this.execute(); } } return CLIENT; } /** * This is used to perform the write part of the negotiation. The read part * is where the server sends information to the client and the client * interprets the data and determines what action to take. After a write the * negotiation typically completes or waits for the next response from the * client. * * @return the next action that should be taken by the handshake */ private Status write() throws IOException { return this.write(5); } /** * This is used to perform the write part of the negotiation. The read part * is where the server sends information to the client and the client * interprets the data and determines what action to take. After a write the * negotiation typically completes or waits for the next response from the * client. * * @param count * this is the number of times a read can repeat * * @return the next action that should be taken by the handshake */ private Status write(int count) throws IOException { while (count > 0) { SSLEngineResult result = this.engine.wrap(this.empty, this.output); HandshakeStatus status = result.getHandshakeStatus(); switch (status) { case NOT_HANDSHAKING: case FINISHED: case NEED_UNWRAP: return SERVER; case NEED_WRAP: return this.write(count - 1); case NEED_TASK: this.execute(); } } return SERVER; } /** * 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 = this.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 * signaled. * * @return this returns true when the message has been read */ @Override public boolean receive() throws IOException { int count = this.input.capacity(); if (count > 0) { this.input.compact(); } int size = this.channel.read(this.input); if (this.trace != null) { this.trace.trace(READ, size); } if (size < 0) throw new TransportException("Client closed connection"); if (count > 0) { this.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 client then this will return true. If however * there is content yet to be sent to the client 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 */ @Override public boolean send() throws IOException { int require = this.output.position(); int count = 0; if (require > 0) { this.output.flip(); } while (count < require) { int size = this.channel.write(this.output); if (this.trace != null) { this.trace.trace(WRITE, size); } if (size <= 0) { break; } count += size; } if (require > 0) { this.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. */ @Override public void commit() throws IOException { Transport secure = new SecureTransport(this.transport, this.output, this.input); if (this.negotiator != null) { this.trace.trace(HANDSHAKE_DONE); this.negotiator.process(secure); } } /** * The <code>Done</code> task is used to transfer the transport created to * the negotiator. 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 Done extends Task { /** * Constructor for the <code>Done</code> task. This is used to pass the * transport object object to the negotiator when the SSL handshake has * completed. * * @param state * this is the underlying negotiation to use * @param trace * the trace that is used to monitor the handshake */ public Done(Negotiation state, Trace trace) { super(state, Handshake.this.negotiator, 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 { this.state.commit(); } } /** * The <code>Client</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 Client extends Task { /** * Constructor for the <code>Client</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 trace * the trace that is used to monitor the handshake */ public Client(Negotiation state, Trace trace) { super(state, Handshake.this.negotiator, 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 this.state.receive(); } } /** * The <code>Server</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 Server extends Task { /** * Constructor for the <code>Server</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 trace * the trace that is used to monitor the handshake */ public Server(Negotiation state, Trace trace) { super(state, Handshake.this.negotiator, trace, OP_WRITE); } /** * This method is used to determine if the task is ready. This is * executed when the select operation is signaled. 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 this.state.send(); } } }