/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.harmony.xnet.provider.jsse; import java.io.IOException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; /** * Implementation of SSLEngine. * @see javax.net.ssl.SSLEngine class documentation for more information. */ public class SSLEngineImpl extends SSLEngine { // indicates if peer mode was set private boolean peer_mode_was_set = false; // indicates if handshake has been started private boolean handshake_started = false; // indicates if inbound operations finished private boolean isInboundDone = false; // indicates if outbound operations finished private boolean isOutboundDone = false; // indicates if close_notify alert had been sent to another peer private boolean close_notify_was_sent = false; // indicates if close_notify alert had been received from another peer private boolean close_notify_was_received = false; // indicates if engine was closed (it means that // all the works on it are done, except (probably) some finalizing work) private boolean engine_was_closed = false; // indicates if engine was shutted down (it means that // all cleaning work had been done and the engine is not operable) private boolean engine_was_shutteddown = false; // record protocol to be used protected SSLRecordProtocol recordProtocol; // input stream for record protocol private SSLBufferedInput recProtIS; // handshake protocol to be used private HandshakeProtocol handshakeProtocol; // alert protocol to be used private AlertProtocol alertProtocol; // place where application data will be stored private SSLEngineAppData appData; // outcoming application data stream private SSLEngineDataStream dataStream = new SSLEngineDataStream(); // active session object private SSLSessionImpl session; // peer configuration parameters protected SSLParametersImpl sslParameters; // in case of emergency situations when data could not be // placed in destination buffers it will be stored in this // fields private byte[] remaining_wrapped_data = null; private byte[] remaining_hsh_data = null; // logger private Logger.Stream logger = Logger.getStream("engine"); protected SSLEngineImpl(SSLParametersImpl sslParameters) { this.sslParameters = sslParameters; } protected SSLEngineImpl(String host, int port, SSLParametersImpl sslParameters) { super(host, port); this.sslParameters = sslParameters; } /** * Starts the handshake. * @throws SSLException * @see javax.net.ssl.SSLEngine#beginHandshake() method documentation * for more information */ @Override public void beginHandshake() throws SSLException { if (engine_was_closed) { throw new SSLException("Engine has already been closed."); } if (!peer_mode_was_set) { throw new IllegalStateException("Client/Server mode was not set"); } if (!handshake_started) { handshake_started = true; if (getUseClientMode()) { handshakeProtocol = new ClientHandshakeImpl(this); } else { handshakeProtocol = new ServerHandshakeImpl(this); } appData = new SSLEngineAppData(); alertProtocol = new AlertProtocol(); recProtIS = new SSLBufferedInput(); recordProtocol = new SSLRecordProtocol(handshakeProtocol, alertProtocol, recProtIS, appData); } handshakeProtocol.start(); } /** * Closes inbound operations of this engine * @throws SSLException * @see javax.net.ssl.SSLEngine#closeInbound() method documentation * for more information */ @Override public void closeInbound() throws SSLException { if (logger != null) { logger.println("closeInbound() "+isInboundDone); } if (isInboundDone) { return; } isInboundDone = true; engine_was_closed = true; if (handshake_started) { if (!close_notify_was_received) { if (session != null) { session.invalidate(); } alertProtocol.alert(AlertProtocol.FATAL, AlertProtocol.INTERNAL_ERROR); throw new SSLException("Inbound is closed before close_notify " + "alert has been received."); } } else { // engine is closing before initial handshake has been made shutdown(); } } /** * Closes outbound operations of this engine * @see javax.net.ssl.SSLEngine#closeOutbound() method documentation * for more information */ @Override public void closeOutbound() { if (logger != null) { logger.println("closeOutbound() "+isOutboundDone); } if (isOutboundDone) { return; } isOutboundDone = true; if (handshake_started) { // initial handshake had been started alertProtocol.alert(AlertProtocol.WARNING, AlertProtocol.CLOSE_NOTIFY); close_notify_was_sent = true; } else { // engine is closing before initial handshake has been made shutdown(); } engine_was_closed = true; } /** * Returns handshake's delegated tasks to be run * @return the delegated task to be executed. * @see javax.net.ssl.SSLEngine#getDelegatedTask() method documentation * for more information */ @Override public Runnable getDelegatedTask() { return handshakeProtocol.getTask(); } /** * Returns names of supported cipher suites. * @return array of strings containing the names of supported cipher suites * @see javax.net.ssl.SSLEngine#getSupportedCipherSuites() method * documentation for more information */ @Override public String[] getSupportedCipherSuites() { return CipherSuite.getSupportedCipherSuiteNames(); } // --------------- SSLParameters based methods --------------------- /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#getEnabledCipherSuites() method * documentation for more information */ @Override public String[] getEnabledCipherSuites() { return sslParameters.getEnabledCipherSuites(); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#setEnabledCipherSuites(String[]) method * documentation for more information */ @Override public void setEnabledCipherSuites(String[] suites) { sslParameters.setEnabledCipherSuites(suites); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#getSupportedProtocols() method * documentation for more information */ @Override public String[] getSupportedProtocols() { return ProtocolVersion.supportedProtocols.clone(); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#getEnabledProtocols() method * documentation for more information */ @Override public String[] getEnabledProtocols() { return sslParameters.getEnabledProtocols(); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#setEnabledProtocols(String[]) method * documentation for more information */ @Override public void setEnabledProtocols(String[] protocols) { sslParameters.setEnabledProtocols(protocols); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#setUseClientMode(boolean) method * documentation for more information */ @Override public void setUseClientMode(boolean mode) { if (handshake_started) { throw new IllegalArgumentException( "Could not change the mode after the initial handshake has begun."); } sslParameters.setUseClientMode(mode); peer_mode_was_set = true; } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#getUseClientMode() method * documentation for more information */ @Override public boolean getUseClientMode() { return sslParameters.getUseClientMode(); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#setNeedClientAuth(boolean) method * documentation for more information */ @Override public void setNeedClientAuth(boolean need) { sslParameters.setNeedClientAuth(need); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#getNeedClientAuth() method * documentation for more information */ @Override public boolean getNeedClientAuth() { return sslParameters.getNeedClientAuth(); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#setWantClientAuth(boolean) method * documentation for more information */ @Override public void setWantClientAuth(boolean want) { sslParameters.setWantClientAuth(want); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#getWantClientAuth() method * documentation for more information */ @Override public boolean getWantClientAuth() { return sslParameters.getWantClientAuth(); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#setEnableSessionCreation(boolean) method * documentation for more information */ @Override public void setEnableSessionCreation(boolean flag) { sslParameters.setEnableSessionCreation(flag); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#getEnableSessionCreation() method * documentation for more information */ @Override public boolean getEnableSessionCreation() { return sslParameters.getEnableSessionCreation(); } // ----------------------------------------------------------------- /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#getHandshakeStatus() method * documentation for more information */ @Override public SSLEngineResult.HandshakeStatus getHandshakeStatus() { if (!handshake_started || engine_was_shutteddown) { // initial handshake has not been started yet return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; } if (alertProtocol.hasAlert()) { // need to send an alert return SSLEngineResult.HandshakeStatus.NEED_WRAP; } if (close_notify_was_sent && !close_notify_was_received) { // waiting for "close_notify" response return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; } return handshakeProtocol.getStatus(); } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#getSession() method * documentation for more information */ @Override public SSLSession getSession() { if (session != null) { return session; } return SSLSessionImpl.NULL_SESSION; } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#isInboundDone() method * documentation for more information */ @Override public boolean isInboundDone() { return isInboundDone || engine_was_closed; } /** * This method works according to the specification of implemented class. * @see javax.net.ssl.SSLEngine#isOutboundDone() method * documentation for more information */ @Override public boolean isOutboundDone() { return isOutboundDone; } /** * Decodes one complete SSL/TLS record provided in the source buffer. * If decoded record contained application data, this data will * be placed in the destination buffers. * For more information about TLS record fragmentation see * TLS v 1 specification (http://www.ietf.org/rfc/rfc2246.txt) p 6.2. * @param src source buffer containing SSL/TLS record. * @param dsts destination buffers to place received application data. * @see javax.net.ssl.SSLEngine#unwrap(ByteBuffer,ByteBuffer[],int,int) * method documentation for more information */ @Override public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, int offset, int length) throws SSLException { if (engine_was_shutteddown) { return new SSLEngineResult(SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0); } if ((src == null) || (dsts == null)) { throw new IllegalStateException( "Some of the input parameters are null"); } if (!handshake_started) { beginHandshake(); } SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus(); // If is is initial handshake or connection closure stage, // check if this call was made in spite of handshake status if ((session == null || engine_was_closed) && ( handshakeStatus.equals( SSLEngineResult.HandshakeStatus.NEED_WRAP) || handshakeStatus.equals( SSLEngineResult.HandshakeStatus.NEED_TASK))) { return new SSLEngineResult( getEngineStatus(), handshakeStatus, 0, 0); } if (src.remaining() < recordProtocol.getMinRecordSize()) { return new SSLEngineResult( SSLEngineResult.Status.BUFFER_UNDERFLOW, getHandshakeStatus(), 0, 0); } try { src.mark(); // check the destination buffers and count their capacity int capacity = 0; for (int i=offset; i<offset+length; i++) { if (dsts[i] == null) { throw new IllegalStateException( "Some of the input parameters are null"); } if (dsts[i].isReadOnly()) { throw new ReadOnlyBufferException(); } capacity += dsts[i].remaining(); } if (capacity < recordProtocol.getDataSize(src.remaining())) { return new SSLEngineResult( SSLEngineResult.Status.BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); } recProtIS.setSourceBuffer(src); // unwrap the record contained in source buffer, pass it // to appropriate client protocol (alert, handshake, or app) // and retrieve the type of unwrapped data int type = recordProtocol.unwrap(); // process the data and return the result switch (type) { case ContentType.HANDSHAKE: case ContentType.CHANGE_CIPHER_SPEC: if (handshakeProtocol.getStatus().equals( SSLEngineResult.HandshakeStatus.FINISHED)) { session = recordProtocol.getSession(); } break; case ContentType.APPLICATION_DATA: break; case ContentType.ALERT: if (alertProtocol.isFatalAlert()) { alertProtocol.setProcessed(); if (session != null) { session.invalidate(); } String description = "Fatal alert received " + alertProtocol.getAlertDescription(); shutdown(); throw new SSLException(description); } else { if (logger != null) { logger.println("Warning allert has been received: " + alertProtocol.getAlertDescription()); } switch(alertProtocol.getDescriptionCode()) { case AlertProtocol.CLOSE_NOTIFY: alertProtocol.setProcessed(); close_notify_was_received = true; if (!close_notify_was_sent) { closeOutbound(); closeInbound(); } else { closeInbound(); shutdown(); } break; case AlertProtocol.NO_RENEGOTIATION: alertProtocol.setProcessed(); if (session == null) { // message received during the initial // handshake throw new AlertException( AlertProtocol.HANDSHAKE_FAILURE, new SSLHandshakeException( "Received no_renegotiation " + "during the initial handshake")); } else { // just stop the handshake handshakeProtocol.stop(); } break; default: alertProtocol.setProcessed(); } } break; } return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), recProtIS.consumed(), // place the app. data (if any) into the dest. buffers // and get the number of produced bytes: appData.placeTo(dsts, offset, length)); } catch (BufferUnderflowException e) { // there was not enought data ource buffer to make complete packet src.reset(); return new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW, getHandshakeStatus(), 0, 0); } catch (AlertException e) { // fatal alert occured alertProtocol.alert(AlertProtocol.FATAL, e.getDescriptionCode()); engine_was_closed = true; src.reset(); if (session != null) { session.invalidate(); } // shutdown work will be made after the alert will be sent // to another peer (by wrap method) throw e.getReason(); } catch (SSLException e) { throw e; } catch (IOException e) { alertProtocol.alert(AlertProtocol.FATAL, AlertProtocol.INTERNAL_ERROR); engine_was_closed = true; // shutdown work will be made after the alert will be sent // to another peer (by wrap method) throw new SSLException(e.getMessage()); } } /** * Encodes the application data into SSL/TLS record. If handshake status * of the engine differs from NOT_HANDSHAKING the operation can work * without consuming of the source data. * For more information about TLS record fragmentation see * TLS v 1 specification (http://www.ietf.org/rfc/rfc2246.txt) p 6.2. * @param srcs the source buffers with application data to be encoded * into SSL/TLS record. * @param offset the offset in the destination buffers array pointing to * the first buffer with the source data. * @param len specifies the maximum number of buffers to be procesed. * @param dst the destination buffer where encoded data will be placed. * @see javax.net.ssl.SSLEngine#wrap(ByteBuffer[],int,int,ByteBuffer) method * documentation for more information */ @Override public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int len, ByteBuffer dst) throws SSLException { if (engine_was_shutteddown) { return new SSLEngineResult(SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0); } if ((srcs == null) || (dst == null)) { throw new IllegalStateException( "Some of the input parameters are null"); } if (dst.isReadOnly()) { throw new ReadOnlyBufferException(); } if (!handshake_started) { beginHandshake(); } SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus(); // If it is an initial handshake or connection closure stage, // check if this call was made in spite of handshake status if ((session == null || engine_was_closed) && ( handshakeStatus.equals( SSLEngineResult.HandshakeStatus.NEED_UNWRAP) || handshakeStatus.equals( SSLEngineResult.HandshakeStatus.NEED_TASK))) { return new SSLEngineResult( getEngineStatus(), handshakeStatus, 0, 0); } int capacity = dst.remaining(); int produced = 0; if (alertProtocol.hasAlert()) { // we have an alert to be sent if (capacity < recordProtocol.getRecordSize(2)) { return new SSLEngineResult( SSLEngineResult.Status.BUFFER_OVERFLOW, handshakeStatus, 0, 0); } byte[] alert_data = alertProtocol.wrap(); // place the alert record into destination dst.put(alert_data); if (alertProtocol.isFatalAlert()) { alertProtocol.setProcessed(); if (session != null) { session.invalidate(); } // fatal alert has been sent, so shut down the engine shutdown(); return new SSLEngineResult( SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, alert_data.length); } else { alertProtocol.setProcessed(); // check if the works on this engine have been done if (close_notify_was_sent && close_notify_was_received) { shutdown(); return new SSLEngineResult(SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, alert_data.length); } return new SSLEngineResult( getEngineStatus(), getHandshakeStatus(), 0, alert_data.length); } } if (capacity < recordProtocol.getMinRecordSize()) { if (logger != null) { logger.println("Capacity of the destination(" +capacity+") < MIN_PACKET_SIZE(" +recordProtocol.getMinRecordSize()+")"); } return new SSLEngineResult(SSLEngineResult.Status.BUFFER_OVERFLOW, handshakeStatus, 0, 0); } try { if (!handshakeStatus.equals( SSLEngineResult.HandshakeStatus.NEED_WRAP)) { // so we wraps application data dataStream.setSourceBuffers(srcs, offset, len); if ((capacity < SSLRecordProtocol.MAX_SSL_PACKET_SIZE) && (capacity < recordProtocol.getRecordSize( dataStream.available()))) { if (logger != null) { logger.println("The destination buffer(" +capacity+") can not take the resulting packet(" + recordProtocol.getRecordSize( dataStream.available())+")"); } return new SSLEngineResult( SSLEngineResult.Status.BUFFER_OVERFLOW, handshakeStatus, 0, 0); } if (remaining_wrapped_data == null) { remaining_wrapped_data = recordProtocol.wrap(ContentType.APPLICATION_DATA, dataStream); } if (capacity < remaining_wrapped_data.length) { // It should newer happen because we checked the destination // buffer size, but there is a possibility // (if dest buffer was filled outside) // so we just remember the data into remaining_wrapped_data // and will enclose it during the the next call return new SSLEngineResult( SSLEngineResult.Status.BUFFER_OVERFLOW, handshakeStatus, dataStream.consumed(), 0); } else { dst.put(remaining_wrapped_data); produced = remaining_wrapped_data.length; remaining_wrapped_data = null; return new SSLEngineResult(getEngineStatus(), handshakeStatus, dataStream.consumed(), produced); } } else { if (remaining_hsh_data == null) { remaining_hsh_data = handshakeProtocol.wrap(); } if (capacity < remaining_hsh_data.length) { // It should newer happen because we checked the destination // buffer size, but there is a possibility // (if dest buffer was filled outside) // so we just remember the data into remaining_hsh_data // and will enclose it during the the next call return new SSLEngineResult( SSLEngineResult.Status.BUFFER_OVERFLOW, handshakeStatus, 0, 0); } else { dst.put(remaining_hsh_data); produced = remaining_hsh_data.length; remaining_hsh_data = null; handshakeStatus = handshakeProtocol.getStatus(); if (handshakeStatus.equals( SSLEngineResult.HandshakeStatus.FINISHED)) { session = recordProtocol.getSession(); } } return new SSLEngineResult( getEngineStatus(), getHandshakeStatus(), 0, produced); } } catch (AlertException e) { // fatal alert occured alertProtocol.alert(AlertProtocol.FATAL, e.getDescriptionCode()); engine_was_closed = true; if (session != null) { session.invalidate(); } // shutdown work will be made after the alert will be sent // to another peer (by wrap method) throw e.getReason(); } } // Shutdownes the engine and makes all cleanup work. private void shutdown() { engine_was_closed = true; engine_was_shutteddown = true; isOutboundDone = true; isInboundDone = true; if (handshake_started) { alertProtocol.shutdown(); alertProtocol = null; handshakeProtocol.shutdown(); handshakeProtocol = null; recordProtocol.shutdown(); recordProtocol = null; } } private SSLEngineResult.Status getEngineStatus() { return (engine_was_closed) ? SSLEngineResult.Status.CLOSED : SSLEngineResult.Status.OK; } }