blob: af033258388218d7b299c4f7f4a43f1d19fe603a [file] [log] [blame]
/*
* 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;
}
}