blob: cf09a6bcc5201cbe3cbee09f4d120c65640c365c [file] [log] [blame]
/*
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLKeyException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.SSLSession;
/**
* Implementation of an non-blocking SSLEngine.
*
* @author Brad Wetmore
*/
final class SSLEngineImpl extends SSLEngine implements SSLTransport {
private final SSLContextImpl sslContext;
final TransportContext conContext;
/**
* Constructor for an SSLEngine from SSLContext, without
* host/port hints.
*
* This Engine will not be able to cache sessions, but must renegotiate
* everything by hand.
*/
SSLEngineImpl(SSLContextImpl sslContext) {
this(sslContext, null, -1);
}
/**
* Constructor for an SSLEngine from SSLContext.
*/
SSLEngineImpl(SSLContextImpl sslContext,
String host, int port) {
super(host, port);
this.sslContext = sslContext;
HandshakeHash handshakeHash = new HandshakeHash();
if (sslContext.isDTLS()) {
this.conContext = new TransportContext(sslContext, this,
new DTLSInputRecord(handshakeHash),
new DTLSOutputRecord(handshakeHash));
} else {
this.conContext = new TransportContext(sslContext, this,
new SSLEngineInputRecord(handshakeHash),
new SSLEngineOutputRecord(handshakeHash));
}
// Server name indication is a connection scope extension.
if (host != null) {
this.conContext.sslConfig.serverNames =
Utilities.addToSNIServerNameList(
conContext.sslConfig.serverNames, host);
}
}
@Override
public synchronized void beginHandshake() throws SSLException {
if (conContext.isUnsureMode) {
throw new IllegalStateException(
"Client/Server mode has not yet been set.");
}
try {
conContext.kickstart();
} catch (IOException ioe) {
throw conContext.fatal(Alert.HANDSHAKE_FAILURE,
"Couldn't kickstart handshaking", ioe);
} catch (Exception ex) { // including RuntimeException
throw conContext.fatal(Alert.INTERNAL_ERROR,
"Fail to begin handshake", ex);
}
}
@Override
public synchronized SSLEngineResult wrap(ByteBuffer[] appData,
int offset, int length, ByteBuffer netData) throws SSLException {
return wrap(appData, offset, length, new ByteBuffer[]{ netData }, 0, 1);
}
// @Override
public synchronized SSLEngineResult wrap(
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws SSLException {
if (conContext.isUnsureMode) {
throw new IllegalStateException(
"Client/Server mode has not yet been set.");
}
// See if the handshaker needs to report back some SSLException.
checkTaskThrown();
// check parameters
checkParams(srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
try {
return writeRecord(
srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
} catch (SSLProtocolException spe) {
// may be an unexpected handshake message
throw conContext.fatal(Alert.UNEXPECTED_MESSAGE, spe);
} catch (IOException ioe) {
throw conContext.fatal(Alert.INTERNAL_ERROR,
"problem wrapping app data", ioe);
} catch (Exception ex) { // including RuntimeException
throw conContext.fatal(Alert.INTERNAL_ERROR,
"Fail to wrap application data", ex);
}
}
private SSLEngineResult writeRecord(
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
// May need to deliver cached records.
if (isOutboundDone()) {
return new SSLEngineResult(
Status.CLOSED, getHandshakeStatus(), 0, 0);
}
HandshakeContext hc = conContext.handshakeContext;
HandshakeStatus hsStatus = null;
if (!conContext.isNegotiated && !conContext.isBroken &&
!conContext.isInboundClosed() &&
!conContext.isOutboundClosed()) {
conContext.kickstart();
hsStatus = getHandshakeStatus();
if (hsStatus == HandshakeStatus.NEED_UNWRAP) {
/*
* For DTLS, if the handshake state is
* HandshakeStatus.NEED_UNWRAP, a call to SSLEngine.wrap()
* means that the previous handshake packets (if delivered)
* get lost, and need retransmit the handshake messages.
*/
if (!sslContext.isDTLS() || hc == null ||
!hc.sslConfig.enableRetransmissions ||
conContext.outputRecord.firstMessage) {
return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
} // otherwise, need retransmission
}
}
if (hsStatus == null) {
hsStatus = getHandshakeStatus();
}
/*
* If we have a task outstanding, this *MUST* be done before
* doing any more wrapping, because we could be in the middle
* of receiving a handshake message, for example, a finished
* message which would change the ciphers.
*/
if (hsStatus == HandshakeStatus.NEED_TASK) {
return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
}
int dstsRemains = 0;
for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
dstsRemains += dsts[i].remaining();
}
// Check destination buffer size.
//
// We can be smarter about using smaller buffer sizes later. For
// now, force it to be large enough to handle any valid record.
if (dstsRemains < conContext.conSession.getPacketBufferSize()) {
return new SSLEngineResult(
Status.BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0);
}
int srcsRemains = 0;
for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
srcsRemains += srcs[i].remaining();
}
Ciphertext ciphertext = null;
try {
// Acquire the buffered to-be-delivered records or retransmissions.
//
// May have buffered records, or need retransmission if handshaking.
if (!conContext.outputRecord.isEmpty() || (hc != null &&
hc.sslConfig.enableRetransmissions &&
hc.sslContext.isDTLS() &&
hsStatus == HandshakeStatus.NEED_UNWRAP)) {
ciphertext = encode(null, 0, 0,
dsts, dstsOffset, dstsLength);
}
if (ciphertext == null && srcsRemains != 0) {
ciphertext = encode(srcs, srcsOffset, srcsLength,
dsts, dstsOffset, dstsLength);
}
} catch (IOException ioe) {
if (ioe instanceof SSLException) {
throw ioe;
} else {
throw new SSLException("Write problems", ioe);
}
}
/*
* Check for status.
*/
Status status = (isOutboundDone() ? Status.CLOSED : Status.OK);
if (ciphertext != null && ciphertext.handshakeStatus != null) {
hsStatus = ciphertext.handshakeStatus;
} else {
hsStatus = getHandshakeStatus();
if (ciphertext == null && !conContext.isNegotiated &&
conContext.isInboundClosed() &&
hsStatus == HandshakeStatus.NEED_WRAP) {
// Even the outboud is open, no futher data could be wrapped as:
// 1. the outbound is empty
// 2. no negotiated connection
// 3. the inbound has closed, cannot complete the handshake
//
// Mark the engine as closed if the handshake status is
// NEED_WRAP. Otherwise, it could lead to dead loops in
// applications.
status = Status.CLOSED;
}
}
int deltaSrcs = srcsRemains;
for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
deltaSrcs -= srcs[i].remaining();
}
int deltaDsts = dstsRemains;
for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
deltaDsts -= dsts[i].remaining();
}
return new SSLEngineResult(status, hsStatus, deltaSrcs, deltaDsts,
ciphertext != null ? ciphertext.recordSN : -1L);
}
private Ciphertext encode(
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
Ciphertext ciphertext = null;
try {
ciphertext = conContext.outputRecord.encode(
srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
} catch (SSLHandshakeException she) {
// may be record sequence number overflow
throw conContext.fatal(Alert.HANDSHAKE_FAILURE, she);
} catch (IOException e) {
throw conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
}
if (ciphertext == null) {
return null;
}
// Is the handshake completed?
boolean needRetransmission =
conContext.sslContext.isDTLS() &&
conContext.handshakeContext != null &&
conContext.handshakeContext.sslConfig.enableRetransmissions;
HandshakeStatus hsStatus =
tryToFinishHandshake(ciphertext.contentType);
if (needRetransmission &&
hsStatus == HandshakeStatus.FINISHED &&
conContext.sslContext.isDTLS() &&
ciphertext.handshakeType == SSLHandshake.FINISHED.id) {
// Retransmit the last flight for DTLS.
//
// The application data transactions may begin immediately
// after the last flight. If the last flight get lost, the
// application data may be discarded accordingly. As could
// be an issue for some applications. This impact can be
// mitigated by sending the last fligth twice.
if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) {
SSLLogger.finest("retransmit the last flight messages");
}
conContext.outputRecord.launchRetransmission();
hsStatus = HandshakeStatus.NEED_WRAP;
}
if (hsStatus == null) {
hsStatus = conContext.getHandshakeStatus();
}
// Is the sequence number is nearly overflow?
if (conContext.outputRecord.seqNumIsHuge() ||
conContext.outputRecord.writeCipher.atKeyLimit()) {
hsStatus = tryKeyUpdate(hsStatus);
}
// update context status
ciphertext.handshakeStatus = hsStatus;
return ciphertext;
}
private HandshakeStatus tryToFinishHandshake(byte contentType) {
HandshakeStatus hsStatus = null;
if ((contentType == ContentType.HANDSHAKE.id) &&
conContext.outputRecord.isEmpty()) {
if (conContext.handshakeContext == null) {
hsStatus = HandshakeStatus.FINISHED;
} else if (conContext.isPostHandshakeContext()) {
// unlikely, but just in case.
hsStatus = conContext.finishPostHandshake();
} else if (conContext.handshakeContext.handshakeFinished) {
hsStatus = conContext.finishHandshake();
}
} // Otherwise, the followed call to getHSStatus() will help.
return hsStatus;
}
/**
* Try key update for sequence number wrap or key usage limit.
*
* Note that in order to maintain the handshake status properly, we check
* the sequence number and key usage limit after the last record
* reading/writing process.
*
* As we request renegotiation or close the connection for wrapped sequence
* number when there is enough sequence number space left to handle a few
* more records, so the sequence number of the last record cannot be
* wrapped.
*/
private HandshakeStatus tryKeyUpdate(
HandshakeStatus currentHandshakeStatus) throws IOException {
// Don't bother to kickstart if handshaking is in progress, or if the
// connection is not duplex-open.
if ((conContext.handshakeContext == null) &&
!conContext.isOutboundClosed() &&
!conContext.isInboundClosed() &&
!conContext.isBroken) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest("trigger key update");
}
beginHandshake();
return conContext.getHandshakeStatus();
}
return currentHandshakeStatus;
}
private static void checkParams(
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
ByteBuffer[] dsts, int dstsOffset, int dstsLength) {
if ((srcs == null) || (dsts == null)) {
throw new IllegalArgumentException(
"source or destination buffer is null");
}
if ((dstsOffset < 0) || (dstsLength < 0) ||
(dstsOffset > dsts.length - dstsLength)) {
throw new IndexOutOfBoundsException(
"index out of bound of the destination buffers");
}
if ((srcsOffset < 0) || (srcsLength < 0) ||
(srcsOffset > srcs.length - srcsLength)) {
throw new IndexOutOfBoundsException(
"index out of bound of the source buffers");
}
for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
if (dsts[i] == null) {
throw new IllegalArgumentException(
"destination buffer[" + i + "] == null");
}
/*
* Make sure the destination bufffers are writable.
*/
if (dsts[i].isReadOnly()) {
throw new ReadOnlyBufferException();
}
}
for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
if (srcs[i] == null) {
throw new IllegalArgumentException(
"source buffer[" + i + "] == null");
}
}
}
@Override
public synchronized SSLEngineResult unwrap(ByteBuffer src,
ByteBuffer[] dsts, int offset, int length) throws SSLException {
return unwrap(
new ByteBuffer[]{src}, 0, 1, dsts, offset, length);
}
// @Override
public synchronized SSLEngineResult unwrap(
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws SSLException {
if (conContext.isUnsureMode) {
throw new IllegalStateException(
"Client/Server mode has not yet been set.");
}
// See if the handshaker needs to report back some SSLException.
checkTaskThrown();
// check parameters
checkParams(srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
try {
return readRecord(
srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
} catch (SSLProtocolException spe) {
// may be an unexpected handshake message
throw conContext.fatal(Alert.UNEXPECTED_MESSAGE,
spe.getMessage(), spe);
} catch (IOException ioe) {
/*
* Don't reset position so it looks like we didn't
* consume anything. We did consume something, and it
* got us into this situation, so report that much back.
* Our days of consuming are now over anyway.
*/
throw conContext.fatal(Alert.INTERNAL_ERROR,
"problem unwrapping net record", ioe);
} catch (Exception ex) { // including RuntimeException
throw conContext.fatal(Alert.INTERNAL_ERROR,
"Fail to unwrap network record", ex);
}
}
private SSLEngineResult readRecord(
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
/*
* Check if we are closing/closed.
*/
if (isInboundDone()) {
return new SSLEngineResult(
Status.CLOSED, getHandshakeStatus(), 0, 0);
}
HandshakeStatus hsStatus = null;
if (!conContext.isNegotiated && !conContext.isBroken &&
!conContext.isInboundClosed() &&
!conContext.isOutboundClosed()) {
conContext.kickstart();
/*
* If there's still outbound data to flush, we
* can return without trying to unwrap anything.
*/
hsStatus = getHandshakeStatus();
if (hsStatus == HandshakeStatus.NEED_WRAP) {
return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
}
}
if (hsStatus == null) {
hsStatus = getHandshakeStatus();
}
/*
* If we have a task outstanding, this *MUST* be done before
* doing any more unwrapping, because we could be in the middle
* of receiving a handshake message, for example, a finished
* message which would change the ciphers.
*/
if (hsStatus == HandshakeStatus.NEED_TASK) {
return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
}
if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
Plaintext plainText = null;
try {
plainText = decode(null, 0, 0,
dsts, dstsOffset, dstsLength);
} catch (IOException ioe) {
if (ioe instanceof SSLException) {
throw ioe;
} else {
throw new SSLException("readRecord", ioe);
}
}
Status status = (isInboundDone() ? Status.CLOSED : Status.OK);
if (plainText.handshakeStatus != null) {
hsStatus = plainText.handshakeStatus;
} else {
hsStatus = getHandshakeStatus();
}
return new SSLEngineResult(
status, hsStatus, 0, 0, plainText.recordSN);
}
int srcsRemains = 0;
for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
srcsRemains += srcs[i].remaining();
}
if (srcsRemains == 0) {
return new SSLEngineResult(
Status.BUFFER_UNDERFLOW, hsStatus, 0, 0);
}
/*
* Check the packet to make sure enough is here.
* This will also indirectly check for 0 len packets.
*/
int packetLen = 0;
try {
packetLen = conContext.inputRecord.bytesInCompletePacket(
srcs, srcsOffset, srcsLength);
} catch (SSLException ssle) {
// Need to discard invalid records for DTLS protocols.
if (sslContext.isDTLS()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) {
SSLLogger.finest("Discard invalid DTLS records", ssle);
}
// invalid, discard the entire data [section 4.1.2.7, RFC 6347]
int deltaNet = 0;
// int deltaNet = netData.remaining();
// netData.position(netData.limit());
Status status = (isInboundDone() ? Status.CLOSED : Status.OK);
if (hsStatus == null) {
hsStatus = getHandshakeStatus();
}
return new SSLEngineResult(status, hsStatus, deltaNet, 0, -1L);
} else {
throw ssle;
}
}
// Is this packet bigger than SSL/TLS normally allows?
if (packetLen > conContext.conSession.getPacketBufferSize()) {
int largestRecordSize = sslContext.isDTLS() ?
DTLSRecord.maxRecordSize : SSLRecord.maxLargeRecordSize;
if ((packetLen <= largestRecordSize) && !sslContext.isDTLS()) {
// Expand the expected maximum packet/application buffer
// sizes.
//
// Only apply to SSL/TLS protocols.
// Old behavior: shall we honor the System Property
// "jsse.SSLEngine.acceptLargeFragments" if it is "false"?
conContext.conSession.expandBufferSizes();
}
// check the packet again
largestRecordSize = conContext.conSession.getPacketBufferSize();
if (packetLen > largestRecordSize) {
throw new SSLProtocolException(
"Input record too big: max = " +
largestRecordSize + " len = " + packetLen);
}
}
/*
* Check for OVERFLOW.
*
* Delay enforcing the application buffer free space requirement
* until after the initial handshaking.
*/
int dstsRemains = 0;
for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
dstsRemains += dsts[i].remaining();
}
if (conContext.isNegotiated) {
int FragLen =
conContext.inputRecord.estimateFragmentSize(packetLen);
if (FragLen > dstsRemains) {
return new SSLEngineResult(
Status.BUFFER_OVERFLOW, hsStatus, 0, 0);
}
}
// check for UNDERFLOW.
if ((packetLen == -1) || (srcsRemains < packetLen)) {
return new SSLEngineResult(Status.BUFFER_UNDERFLOW, hsStatus, 0, 0);
}
/*
* We're now ready to actually do the read.
*/
Plaintext plainText = null;
try {
plainText = decode(srcs, srcsOffset, srcsLength,
dsts, dstsOffset, dstsLength);
} catch (IOException ioe) {
if (ioe instanceof SSLException) {
throw ioe;
} else {
throw new SSLException("readRecord", ioe);
}
}
/*
* Check the various condition that we could be reporting.
*
* It's *possible* something might have happened between the
* above and now, but it was better to minimally lock "this"
* during the read process. We'll return the current
* status, which is more representative of the current state.
*
* status above should cover: FINISHED, NEED_TASK
*/
Status status = (isInboundDone() ? Status.CLOSED : Status.OK);
if (plainText.handshakeStatus != null) {
hsStatus = plainText.handshakeStatus;
} else {
hsStatus = getHandshakeStatus();
}
int deltaNet = srcsRemains;
for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) {
deltaNet -= srcs[i].remaining();
}
int deltaApp = dstsRemains;
for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) {
deltaApp -= dsts[i].remaining();
}
return new SSLEngineResult(
status, hsStatus, deltaNet, deltaApp, plainText.recordSN);
}
private Plaintext decode(
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
Plaintext pt = SSLTransport.decode(conContext,
srcs, srcsOffset, srcsLength,
dsts, dstsOffset, dstsLength);
// Is the handshake completed?
if (pt != Plaintext.PLAINTEXT_NULL) {
HandshakeStatus hsStatus = tryToFinishHandshake(pt.contentType);
if (hsStatus == null) {
pt.handshakeStatus = conContext.getHandshakeStatus();
} else {
pt.handshakeStatus = hsStatus;
}
// Is the sequence number is nearly overflow?
if (conContext.inputRecord.seqNumIsHuge() ||
conContext.inputRecord.readCipher.atKeyLimit()) {
pt.handshakeStatus =
tryKeyUpdate(pt.handshakeStatus);
}
}
return pt;
}
@Override
public synchronized Runnable getDelegatedTask() {
if (conContext.handshakeContext != null && // PRE or POST handshake
!conContext.handshakeContext.taskDelegated &&
!conContext.handshakeContext.delegatedActions.isEmpty()) {
conContext.handshakeContext.taskDelegated = true;
return new DelegatedTask(this);
}
return null;
}
@Override
public synchronized void closeInbound() throws SSLException {
if (isInboundDone()) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest("Closing inbound of SSLEngine");
}
// Is it ready to close inbound?
//
// No need to throw exception if the initial handshake is not started.
if (!conContext.isInputCloseNotified &&
(conContext.isNegotiated || conContext.handshakeContext != null)) {
throw conContext.fatal(Alert.INTERNAL_ERROR,
"closing inbound before receiving peer's close_notify");
}
conContext.closeInbound();
}
@Override
public synchronized boolean isInboundDone() {
return conContext.isInboundClosed();
}
@Override
public synchronized void closeOutbound() {
if (conContext.isOutboundClosed()) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest("Closing outbound of SSLEngine");
}
conContext.closeOutbound();
}
@Override
public synchronized boolean isOutboundDone() {
return conContext.isOutboundDone();
}
@Override
public String[] getSupportedCipherSuites() {
return CipherSuite.namesOf(sslContext.getSupportedCipherSuites());
}
@Override
public synchronized String[] getEnabledCipherSuites() {
return CipherSuite.namesOf(conContext.sslConfig.enabledCipherSuites);
}
@Override
public synchronized void setEnabledCipherSuites(String[] suites) {
conContext.sslConfig.enabledCipherSuites =
CipherSuite.validValuesOf(suites);
}
@Override
public String[] getSupportedProtocols() {
return ProtocolVersion.toStringArray(
sslContext.getSupportedProtocolVersions());
}
@Override
public synchronized String[] getEnabledProtocols() {
return ProtocolVersion.toStringArray(
conContext.sslConfig.enabledProtocols);
}
@Override
public synchronized void setEnabledProtocols(String[] protocols) {
if (protocols == null) {
throw new IllegalArgumentException("Protocols cannot be null");
}
conContext.sslConfig.enabledProtocols =
ProtocolVersion.namesOf(protocols);
}
@Override
public synchronized SSLSession getSession() {
return conContext.conSession;
}
@Override
public synchronized SSLSession getHandshakeSession() {
return conContext.handshakeContext == null ?
null : conContext.handshakeContext.handshakeSession;
}
@Override
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
return conContext.getHandshakeStatus();
}
@Override
public synchronized void setUseClientMode(boolean mode) {
conContext.setUseClientMode(mode);
}
@Override
public synchronized boolean getUseClientMode() {
return conContext.sslConfig.isClientMode;
}
@Override
public synchronized void setNeedClientAuth(boolean need) {
conContext.sslConfig.clientAuthType =
(need ? ClientAuthType.CLIENT_AUTH_REQUIRED :
ClientAuthType.CLIENT_AUTH_NONE);
}
@Override
public synchronized boolean getNeedClientAuth() {
return (conContext.sslConfig.clientAuthType ==
ClientAuthType.CLIENT_AUTH_REQUIRED);
}
@Override
public synchronized void setWantClientAuth(boolean want) {
conContext.sslConfig.clientAuthType =
(want ? ClientAuthType.CLIENT_AUTH_REQUESTED :
ClientAuthType.CLIENT_AUTH_NONE);
}
@Override
public synchronized boolean getWantClientAuth() {
return (conContext.sslConfig.clientAuthType ==
ClientAuthType.CLIENT_AUTH_REQUESTED);
}
@Override
public synchronized void setEnableSessionCreation(boolean flag) {
conContext.sslConfig.enableSessionCreation = flag;
}
@Override
public synchronized boolean getEnableSessionCreation() {
return conContext.sslConfig.enableSessionCreation;
}
@Override
public synchronized SSLParameters getSSLParameters() {
return conContext.sslConfig.getSSLParameters();
}
@Override
public synchronized void setSSLParameters(SSLParameters params) {
conContext.sslConfig.setSSLParameters(params);
if (conContext.sslConfig.maximumPacketSize != 0) {
conContext.outputRecord.changePacketSize(
conContext.sslConfig.maximumPacketSize);
}
}
@Override
public synchronized String getApplicationProtocol() {
return conContext.applicationProtocol;
}
@Override
public synchronized String getHandshakeApplicationProtocol() {
return conContext.handshakeContext == null ?
null : conContext.handshakeContext.applicationProtocol;
}
@Override
public synchronized void setHandshakeApplicationProtocolSelector(
BiFunction<SSLEngine, List<String>, String> selector) {
conContext.sslConfig.engineAPSelector = selector;
}
@Override
public synchronized BiFunction<SSLEngine, List<String>, String>
getHandshakeApplicationProtocolSelector() {
return conContext.sslConfig.engineAPSelector;
}
@Override
public boolean useDelegatedTask() {
return true;
}
/*
* Depending on whether the error was just a warning and the
* handshaker wasn't closed, or fatal and the handshaker is now
* null, report back the Exception that happened in the delegated
* task(s).
*/
private synchronized void checkTaskThrown() throws SSLException {
Exception exc = null;
// First check the handshake context.
HandshakeContext hc = conContext.handshakeContext;
if ((hc != null) && (hc.delegatedThrown != null)) {
exc = hc.delegatedThrown;
hc.delegatedThrown = null;
}
/*
* hc.delegatedThrown and conContext.delegatedThrown are most likely
* the same, but it's possible we could have had a non-fatal
* exception and thus the new HandshakeContext is still valid
* (alert warning). If so, then we may have a secondary exception
* waiting to be reported from the TransportContext, so we will
* need to clear that on a successive call. Otherwise, clear it now.
*/
if (conContext.delegatedThrown != null) {
if (exc != null) {
// hc object comparison
if (conContext.delegatedThrown == exc) {
// clear if/only if both are the same
conContext.delegatedThrown = null;
} // otherwise report the hc delegatedThrown
} else {
// Nothing waiting in HandshakeContext, but one is in the
// TransportContext.
exc = conContext.delegatedThrown;
conContext.delegatedThrown = null;
}
}
// Anything to report?
if (exc == null) {
return;
}
// If it wasn't a RuntimeException/SSLException, need to wrap it.
if (exc instanceof SSLException) {
throw (SSLException)exc;
} else if (exc instanceof RuntimeException) {
throw (RuntimeException)exc;
} else {
throw getTaskThrown(exc);
}
}
private static SSLException getTaskThrown(Exception taskThrown) {
String msg = taskThrown.getMessage();
if (msg == null) {
msg = "Delegated task threw Exception or Error";
}
if (taskThrown instanceof RuntimeException) {
throw new RuntimeException(msg, taskThrown);
} else if (taskThrown instanceof SSLHandshakeException) {
return (SSLHandshakeException)
new SSLHandshakeException(msg).initCause(taskThrown);
} else if (taskThrown instanceof SSLKeyException) {
return (SSLKeyException)
new SSLKeyException(msg).initCause(taskThrown);
} else if (taskThrown instanceof SSLPeerUnverifiedException) {
return (SSLPeerUnverifiedException)
new SSLPeerUnverifiedException(msg).initCause(taskThrown);
} else if (taskThrown instanceof SSLProtocolException) {
return (SSLProtocolException)
new SSLProtocolException(msg).initCause(taskThrown);
} else if (taskThrown instanceof SSLException) {
return (SSLException)taskThrown;
} else {
return new SSLException(msg, taskThrown);
}
}
/**
* Implement a simple task delegator.
*/
private static class DelegatedTask implements Runnable {
private final SSLEngineImpl engine;
DelegatedTask(SSLEngineImpl engineInstance) {
this.engine = engineInstance;
}
@Override
public void run() {
synchronized (engine) {
HandshakeContext hc = engine.conContext.handshakeContext;
if (hc == null || hc.delegatedActions.isEmpty()) {
return;
}
try {
AccessController.doPrivileged(
new DelegatedAction(hc), engine.conContext.acc);
} catch (PrivilegedActionException pae) {
// Get the handshake context again in case the
// handshaking has completed.
Exception reportedException = pae.getException();
// Report to both the TransportContext...
if (engine.conContext.delegatedThrown == null) {
engine.conContext.delegatedThrown = reportedException;
}
// ...and the HandshakeContext in case condition
// wasn't fatal and the handshakeContext is still
// around.
hc = engine.conContext.handshakeContext;
if (hc != null) {
hc.delegatedThrown = reportedException;
} else if (engine.conContext.closeReason != null) {
// Update the reason in case there was a previous.
engine.conContext.closeReason =
getTaskThrown(reportedException);
}
} catch (RuntimeException rte) {
// Get the handshake context again in case the
// handshaking has completed.
// Report to both the TransportContext...
if (engine.conContext.delegatedThrown == null) {
engine.conContext.delegatedThrown = rte;
}
// ...and the HandshakeContext in case condition
// wasn't fatal and the handshakeContext is still
// around.
hc = engine.conContext.handshakeContext;
if (hc != null) {
hc.delegatedThrown = rte;
} else if (engine.conContext.closeReason != null) {
// Update the reason in case there was a previous.
engine.conContext.closeReason = rte;
}
}
// Get the handshake context again in case the
// handshaking has completed.
hc = engine.conContext.handshakeContext;
if (hc != null) {
hc.taskDelegated = false;
}
}
}
private static class DelegatedAction
implements PrivilegedExceptionAction<Void> {
final HandshakeContext context;
DelegatedAction(HandshakeContext context) {
this.context = context;
}
@Override
public Void run() throws Exception {
while (!context.delegatedActions.isEmpty()) {
Map.Entry<Byte, ByteBuffer> me =
context.delegatedActions.poll();
if (me != null) {
context.dispatch(me.getKey(), me.getValue());
}
}
return null;
}
}
}
}