8207009: TLS 1.3 half-close and synchronization issues

Reviewed-by: jnimeh, mullan, wetmore
This commit is contained in:
Xue-Lei Andrew Fan 2018-08-14 16:47:56 -07:00
parent a83af4505e
commit 66e8f27bd8
36 changed files with 1467 additions and 621 deletions

View File

@ -118,7 +118,8 @@ public final class SunJCE extends Provider {
final String BLOCK_PADS = "NOPADDING|PKCS5PADDING|ISO10126PADDING";
AccessController.doPrivileged(
new java.security.PrivilegedAction<>() {
new java.security.PrivilegedAction<Object>() {
@Override
public Object run() {
/*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
* 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
@ -145,7 +145,7 @@ import java.util.function.BiFunction;
* application messages are encrypted and integrity protected,
* and inbound messages reverse the process.
*
* <li> Rehandshaking - Either side may request a renegotiation of
* <li> Rehandshaking - Either side may request a renegotiation of
* the session at any time during the Application Data phase. New
* handshaking data can be intermixed among the application data.
* Before starting the rehandshake phase, the application may
@ -156,12 +156,20 @@ import java.util.function.BiFunction;
* configuration settings will not be used until the next
* handshake.
*
* <li> Closure - When the connection is no longer needed, the
* application should close the {@code SSLEngine} and should
* send/receive any remaining messages to the peer before
* closing the underlying transport mechanism. Once an engine is
* closed, it is not reusable: a new {@code SSLEngine} must
* be created.
* <li> Closure - When the connection is no longer needed, the client
* and the server applications should each close both sides of their
* respective connections. For {@code SSLEngine} objects, an
* application should call {@link SSLEngine#closeOutbound()} and
* send any remaining messages to the peer. Likewise, an application
* should receive any remaining messages from the peer before calling
* {@link SSLEngine#closeInbound()}. The underlying transport mechanism
* can then be closed after both sides of the {@code SSLEngine} have
* been closed. If the connection is not closed in an orderly manner
* (for example {@link SSLEngine#closeInbound()} is called before the
* peer's write closure notification has been received), exceptions
* will be raised to indicate that an error has occurred. Once an
* engine is closed, it is not reusable: a new {@code SSLEngine}
* must be created.
* </OL>
* An {@code SSLEngine} is created by calling {@link
* SSLContext#createSSLEngine()} from an initialized

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 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
@ -130,6 +130,21 @@ import java.util.function.BiFunction;
* socket can not switch between client and server modes, even when
* performing renegotiations.
*
* @apiNote
* When the connection is no longer needed, the client and server
* applications should each close both sides of their respective connection.
* For {@code SSLSocket} objects, for example, an application can call
* {@link Socket#shutdownOutput()} or {@link java.io.OutputStream#close()}
* for output strean close and call {@link Socket#shutdownInput()} or
* {@link java.io.InputStream#close()} for input stream close. Note that
* in some cases, closing the input stream may depend on the peer's output
* stream being closed first. If the connection is not closed in an orderly
* manner (for example {@link Socket#shutdownInput()} is called before the
* peer's write closure notification has been received), exceptions may
* be raised to indicate that an error has occurred. Once an
* {@code SSLSocket} is closed, it is not reusable: a new {@code SSLSocket}
* must be created.
*
* @see java.net.Socket
* @see SSLServerSocket
* @see SSLSocketFactory

View File

@ -235,13 +235,22 @@ enum Alert {
Level level = Level.valueOf(am.level);
Alert alert = Alert.valueOf(am.id);
if (alert == Alert.CLOSE_NOTIFY) {
if (tc.handshakeContext != null) {
tc.isInputCloseNotified = true;
tc.closeInbound();
if (tc.peerUserCanceled) {
tc.closeOutbound();
} else if (tc.handshakeContext != null) {
tc.fatal(Alert.UNEXPECTED_MESSAGE,
"Received close_notify during handshake");
}
tc.isInputCloseNotified = true;
tc.closeInbound();
} else if (alert == Alert.USER_CANCELED) {
if (level == Level.WARNING) {
tc.peerUserCanceled = true;
} else {
tc.fatal(alert,
"Received fatal close_notify alert", true, null);
}
} else if ((level == Level.WARNING) && (alert != null)) {
// Terminate the connection if an alert with a level of warning
// is received during handshaking, except the no_certificate

View File

@ -204,30 +204,35 @@ abstract class BaseSSLSocketImpl extends SSLSocket {
//
/**
* The semantics of shutdownInput is not supported in TLS 1.0
* spec. Thus when the method is called on an SSL socket, an
* UnsupportedOperationException will be thrown.
* Places the input stream for this socket at "end of stream". Any data
* sent to the input stream side of the socket is acknowledged and then
* silently discarded.
*
* @throws UnsupportedOperationException
* @see java.net.Socket#shutdownInput
*/
@Override
public final void shutdownInput() throws IOException {
throw new UnsupportedOperationException("The method shutdownInput()" +
" is not supported in SSLSocket");
public void shutdownInput() throws IOException {
if (self == this) {
super.shutdownInput();
} else {
self.shutdownInput();
}
}
/**
* The semantics of shutdownOutput is not supported in TLS 1.0
* spec. Thus when the method is called on an SSL socket, an
* UnsupportedOperationException will be thrown.
* Disables the output stream for this socket. For a TCP socket, any
* previously written data will be sent followed by TCP's normal
* connection termination sequence.
*
* @throws UnsupportedOperationException
* @see java.net.Socket#shutdownOutput
*/
@Override
public final void shutdownOutput() throws IOException {
throw new UnsupportedOperationException("The method shutdownOutput()" +
" is not supported in SSLSocket");
public void shutdownOutput() throws IOException {
if (self == this) {
super.shutdownOutput();
} else {
self.shutdownOutput();
}
}
/**
@ -235,7 +240,7 @@ abstract class BaseSSLSocketImpl extends SSLSocket {
* @see java.net.Socket#isInputShutdown
*/
@Override
public final boolean isInputShutdown() {
public boolean isInputShutdown() {
if (self == this) {
return super.isInputShutdown();
} else {
@ -248,7 +253,7 @@ abstract class BaseSSLSocketImpl extends SSLSocket {
* @see java.net.Socket#isOutputShutdown
*/
@Override
public final boolean isOutputShutdown() {
public boolean isOutputShutdown() {
if (self == this) {
return super.isOutputShutdown();
} else {
@ -618,7 +623,7 @@ abstract class BaseSSLSocketImpl extends SSLSocket {
}
@Override
public synchronized void close() throws IOException {
public void close() throws IOException {
if (self == this) {
super.close();
} else {

View File

@ -805,7 +805,7 @@ enum CipherSuite {
this.id = id;
this.isDefaultEnabled = isDefaultEnabled;
this.name = name;
if (aliases.isEmpty()) {
if (!aliases.isEmpty()) {
this.aliases = Arrays.asList(aliases.split(","));
} else {
this.aliases = Collections.emptyList();

View File

@ -44,7 +44,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
Authenticator prevWriteAuthenticator;
SSLWriteCipher prevWriteCipher;
private final LinkedList<RecordMemo> alertMemos = new LinkedList<>();
private volatile boolean isCloseWaiting = false;
DTLSOutputRecord(HandshakeHash handshakeHash) {
super(handshakeHash, SSLWriteCipher.nullDTlsWriteCipher());
@ -57,6 +57,21 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
this.protocolVersion = ProtocolVersion.NONE;
}
@Override
public synchronized void close() throws IOException {
if (!isClosed) {
if (fragmenter != null && fragmenter.hasAlert()) {
isCloseWaiting = true;
} else {
super.close();
}
}
}
boolean isClosed() {
return isClosed || isCloseWaiting;
}
@Override
void initHandshaker() {
// clean up
@ -71,6 +86,14 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
@Override
void changeWriteCiphers(SSLWriteCipher writeCipher,
boolean useChangeCipherSpec) throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"change_cipher_spec message");
}
return;
}
if (useChangeCipherSpec) {
encodeChangeCipherSpec();
}
@ -91,23 +114,31 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
@Override
void encodeAlert(byte level, byte description) throws IOException {
RecordMemo memo = new RecordMemo();
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"alert message: " + Alert.nameOf(description));
}
return;
}
memo.contentType = ContentType.ALERT.id;
memo.majorVersion = protocolVersion.major;
memo.minorVersion = protocolVersion.minor;
memo.encodeEpoch = writeEpoch;
memo.encodeCipher = writeCipher;
if (fragmenter == null) {
fragmenter = new DTLSFragmenter();
}
memo.fragment = new byte[2];
memo.fragment[0] = level;
memo.fragment[1] = description;
alertMemos.add(memo);
fragmenter.queueUpAlert(level, description);
}
@Override
void encodeChangeCipherSpec() throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"change_cipher_spec message");
}
return;
}
if (fragmenter == null) {
fragmenter = new DTLSFragmenter();
}
@ -117,6 +148,15 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
@Override
void encodeHandshake(byte[] source,
int offset, int length) throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"handshake message",
ByteBuffer.wrap(source, offset, length));
}
return;
}
if (firstMessage) {
firstMessage = false;
}
@ -132,6 +172,23 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
Ciphertext encode(
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
if (isClosed) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"application data or cached messages");
}
return null;
} else if (isCloseWaiting) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"application data");
}
srcs = null; // use no application data.
}
return encode(srcs, srcsOffset, srcsLength, dsts[0]);
}
@ -237,48 +294,6 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
private Ciphertext acquireCiphertext(
ByteBuffer destination) throws IOException {
if (alertMemos != null && !alertMemos.isEmpty()) {
RecordMemo memo = alertMemos.pop();
int dstPos = destination.position();
int dstLim = destination.limit();
int dstContent = dstPos + headerSize +
writeCipher.getExplicitNonceSize();
destination.position(dstContent);
destination.put(memo.fragment);
destination.limit(destination.position());
destination.position(dstContent);
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine(
"WRITE: " + protocolVersion + " " +
ContentType.ALERT.name +
", length = " + destination.remaining());
}
// Encrypt the fragment and wrap up a record.
long recordSN = encrypt(memo.encodeCipher,
ContentType.ALERT.id,
destination, dstPos, dstLim, headerSize,
ProtocolVersion.valueOf(memo.majorVersion,
memo.minorVersion));
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
ByteBuffer temporary = destination.duplicate();
temporary.limit(temporary.position());
temporary.position(dstPos);
SSLLogger.fine("Raw write", temporary);
}
// remain the limit unchanged
destination.limit(dstLim);
return new Ciphertext(ContentType.ALERT.id,
SSLHandshake.NOT_APPLICABLE.id, recordSN);
}
if (fragmenter != null) {
return fragmenter.acquireCiphertext(destination);
}
@ -288,16 +303,14 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
@Override
boolean isEmpty() {
return ((fragmenter == null) || fragmenter.isEmpty()) &&
((alertMemos == null) || alertMemos.isEmpty());
return (fragmenter == null) || fragmenter.isEmpty();
}
@Override
void launchRetransmission() {
// Note: Please don't retransmit if there are handshake messages
// or alerts waiting in the queue.
if (((alertMemos == null) || alertMemos.isEmpty()) &&
(fragmenter != null) && fragmenter.isRetransmittable()) {
if ((fragmenter != null) && fragmenter.isRetransmittable()) {
fragmenter.setRetransmission();
}
}
@ -338,29 +351,6 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
// size is bigger than 256 bytes.
private int retransmits = 2; // attemps of retransmits
void queueUpChangeCipherSpec() {
// Cleanup if a new flight starts.
if (flightIsReady) {
handshakeMemos.clear();
acquireIndex = 0;
flightIsReady = false;
}
RecordMemo memo = new RecordMemo();
memo.contentType = ContentType.CHANGE_CIPHER_SPEC.id;
memo.majorVersion = protocolVersion.major;
memo.minorVersion = protocolVersion.minor;
memo.encodeEpoch = writeEpoch;
memo.encodeCipher = writeCipher;
memo.fragment = new byte[1];
memo.fragment[0] = 1;
handshakeMemos.add(memo);
}
void queueUpHandshake(byte[] buf,
int offset, int length) throws IOException {
@ -401,6 +391,45 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
}
}
void queueUpChangeCipherSpec() {
// Cleanup if a new flight starts.
if (flightIsReady) {
handshakeMemos.clear();
acquireIndex = 0;
flightIsReady = false;
}
RecordMemo memo = new RecordMemo();
memo.contentType = ContentType.CHANGE_CIPHER_SPEC.id;
memo.majorVersion = protocolVersion.major;
memo.minorVersion = protocolVersion.minor;
memo.encodeEpoch = writeEpoch;
memo.encodeCipher = writeCipher;
memo.fragment = new byte[1];
memo.fragment[0] = 1;
handshakeMemos.add(memo);
}
void queueUpAlert(byte level, byte description) throws IOException {
RecordMemo memo = new RecordMemo();
memo.contentType = ContentType.ALERT.id;
memo.majorVersion = protocolVersion.major;
memo.minorVersion = protocolVersion.minor;
memo.encodeEpoch = writeEpoch;
memo.encodeCipher = writeCipher;
memo.fragment = new byte[2];
memo.fragment[0] = level;
memo.fragment[1] = description;
handshakeMemos.add(memo);
}
Ciphertext acquireCiphertext(ByteBuffer dstBuf) throws IOException {
if (isEmpty()) {
if (isRetransmittable()) {
@ -500,8 +529,13 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
return new Ciphertext(hsMemo.contentType,
hsMemo.handshakeType, recordSN);
} else {
if (isCloseWaiting &&
memo.contentType == ContentType.ALERT.id) {
close();
}
acquireIndex++;
return new Ciphertext(ContentType.CHANGE_CIPHER_SPEC.id,
return new Ciphertext(memo.contentType,
SSLHandshake.NOT_APPLICABLE.id, recordSN);
}
}
@ -552,6 +586,16 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
return false;
}
boolean hasAlert() {
for (RecordMemo memo : handshakeMemos) {
if (memo.contentType == ContentType.ALERT.id) {
return true;
}
}
return false;
}
boolean isRetransmittable() {
return (flightIsReady && !handshakeMemos.isEmpty() &&
(acquireIndex >= handshakeMemos.size()));

View File

@ -47,7 +47,6 @@ import sun.security.ssl.SupportedGroupsExtension.NamedGroup;
import sun.security.ssl.SupportedGroupsExtension.NamedGroupType;
import static sun.security.ssl.SupportedGroupsExtension.NamedGroupType.*;
import sun.security.ssl.SupportedGroupsExtension.SupportedGroups;
import sun.security.ssl.PskKeyExchangeModesExtension.PskKeyExchangeMode;
abstract class HandshakeContext implements ConnectionContext {
// System properties

View File

@ -27,6 +27,7 @@ package sun.security.ssl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Output stream for handshake data. This is used only internally
@ -57,7 +58,14 @@ public class HandshakeOutStream extends ByteArrayOutputStream {
}
if (outputRecord != null) {
outputRecord.encodeHandshake(buf, 0, count);
if (!outputRecord.isClosed()) {
outputRecord.encodeHandshake(buf, 0, count);
} else {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"handshake messages", ByteBuffer.wrap(buf, 0, count));
}
}
// reset the byte array output stream
reset();

View File

@ -223,8 +223,8 @@ final class KeyUpdate {
Authenticator.valueOf(hc.conContext.protocolVersion),
hc.conContext.protocolVersion, key, ivSpec,
hc.sslContext.getSecureRandom());
rc.baseSecret = nplus1;
hc.conContext.inputRecord.changeReadCiphers(rc);
hc.conContext.inputRecord.readCipher.baseSecret = nplus1;
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("KeyUpdate: read key updated");
}
@ -303,13 +303,12 @@ final class KeyUpdate {
return null;
}
// Output the handshake message.
km.write(hc.handshakeOutput);
hc.handshakeOutput.flush();
// change write cipher
hc.conContext.outputRecord.changeWriteCiphers(wc, false);
hc.conContext.outputRecord.writeCipher.baseSecret = nplus1;
// Output the handshake message and change the write cipher.
//
// The KeyUpdate handshake message SHALL be delivered in the
// changeWriteCiphers() implementation.
wc.baseSecret = nplus1;
hc.conContext.outputRecord.changeWriteCiphers(wc, km.status.id);
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("KeyUpdate: write key updated");
}

View File

@ -66,7 +66,7 @@ abstract class OutputRecord
int fragmentSize;
// closed or not?
boolean isClosed;
volatile boolean isClosed;
/*
* Mappings from V3 cipher suite encodings to their pure V2 equivalents.
@ -76,6 +76,8 @@ abstract class OutputRecord
{-1, -1, -1, 0x02, 0x01, -1, 0x04, 0x05, -1, 0x06, 0x07};
private static final int[] V3toV2CipherMap3 =
{-1, -1, -1, 0x80, 0x80, -1, 0x80, 0x80, -1, 0x40, 0xC0};
private static final byte[] HANDSHAKE_MESSAGE_KEY_UPDATE =
{SSLHandshake.KEY_UPDATE.id, 0x00, 0x00, 0x01, 0x00};
OutputRecord(HandshakeHash handshakeHash, SSLWriteCipher writeCipher) {
this.writeCipher = writeCipher;
@ -87,7 +89,7 @@ abstract class OutputRecord
// Please set packetSize and protocolVersion in the implementation.
}
void setVersion(ProtocolVersion protocolVersion) {
synchronized void setVersion(ProtocolVersion protocolVersion) {
this.protocolVersion = protocolVersion;
}
@ -106,7 +108,7 @@ abstract class OutputRecord
return false;
}
boolean seqNumIsHuge() {
synchronized boolean seqNumIsHuge() {
return (writeCipher.authenticator != null) &&
writeCipher.authenticator.seqNumIsHuge();
}
@ -145,8 +147,17 @@ abstract class OutputRecord
throw new UnsupportedOperationException();
}
void changeWriteCiphers(SSLWriteCipher writeCipher,
// Change write ciphers, may use change_cipher_spec record.
synchronized void changeWriteCiphers(SSLWriteCipher writeCipher,
boolean useChangeCipherSpec) throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"change_cipher_spec message");
}
return;
}
if (useChangeCipherSpec) {
encodeChangeCipherSpec();
}
@ -165,15 +176,39 @@ abstract class OutputRecord
this.isFirstAppOutputRecord = true;
}
void changePacketSize(int packetSize) {
// Change write ciphers using key_update handshake message.
synchronized void changeWriteCiphers(SSLWriteCipher writeCipher,
byte keyUpdateRequest) throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"key_update handshake message");
}
return;
}
// encode the handshake message, KeyUpdate
byte[] hm = HANDSHAKE_MESSAGE_KEY_UPDATE.clone();
hm[hm.length - 1] = keyUpdateRequest;
encodeHandshake(hm, 0, hm.length);
flush();
// Dispose of any intermediate state in the underlying cipher.
writeCipher.dispose();
this.writeCipher = writeCipher;
this.isFirstAppOutputRecord = true;
}
synchronized void changePacketSize(int packetSize) {
this.packetSize = packetSize;
}
void changeFragmentSize(int fragmentSize) {
synchronized void changeFragmentSize(int fragmentSize) {
this.fragmentSize = fragmentSize;
}
int getMaxPacketSize() {
synchronized int getMaxPacketSize() {
return packetSize;
}
@ -194,13 +229,15 @@ abstract class OutputRecord
@Override
public synchronized void close() throws IOException {
if (!isClosed) {
isClosed = true;
writeCipher.dispose();
if (isClosed) {
return;
}
isClosed = true;
writeCipher.dispose();
}
synchronized boolean isClosed() {
boolean isClosed() {
return isClosed;
}
@ -241,7 +278,7 @@ abstract class OutputRecord
}
}
static long d13Encrypt(
private static long d13Encrypt(
SSLWriteCipher encCipher, byte contentType, ByteBuffer destination,
int headerOffset, int dstLim, int headerSize,
ProtocolVersion protocolVersion) {
@ -282,7 +319,7 @@ abstract class OutputRecord
return Authenticator.toLong(sequenceNumber);
}
static long t13Encrypt(
private static long t13Encrypt(
SSLWriteCipher encCipher, byte contentType, ByteBuffer destination,
int headerOffset, int dstLim, int headerSize,
ProtocolVersion protocolVersion) {
@ -321,7 +358,7 @@ abstract class OutputRecord
return Authenticator.toLong(sequenceNumber);
}
static long t10Encrypt(
private static long t10Encrypt(
SSLWriteCipher encCipher, byte contentType, ByteBuffer destination,
int headerOffset, int dstLim, int headerSize,
ProtocolVersion protocolVersion) {
@ -362,7 +399,7 @@ abstract class OutputRecord
private static final byte[] zeros = new byte[16];
}
long t13Encrypt(
private long t13Encrypt(
SSLWriteCipher encCipher, byte contentType, int headerSize) {
if (!encCipher.isNullCipher()) {
// inner plaintext
@ -375,9 +412,10 @@ abstract class OutputRecord
int contentLen = count - position;
// ensure the capacity
int packetSize = encCipher.calculatePacketSize(contentLen, headerSize);
if (packetSize > buf.length) {
byte[] newBuf = new byte[packetSize];
int requiredPacketSize =
encCipher.calculatePacketSize(contentLen, headerSize);
if (requiredPacketSize > buf.length) {
byte[] newBuf = new byte[requiredPacketSize];
System.arraycopy(buf, 0, newBuf, 0, count);
buf = newBuf;
}
@ -406,16 +444,17 @@ abstract class OutputRecord
return Authenticator.toLong(sequenceNumber);
}
long t10Encrypt(
private long t10Encrypt(
SSLWriteCipher encCipher, byte contentType, int headerSize) {
byte[] sequenceNumber = encCipher.authenticator.sequenceNumber();
int position = headerSize + writeCipher.getExplicitNonceSize();
int contentLen = count - position;
// ensure the capacity
int packetSize = encCipher.calculatePacketSize(contentLen, headerSize);
if (packetSize > buf.length) {
byte[] newBuf = new byte[packetSize];
int requiredPacketSize =
encCipher.calculatePacketSize(contentLen, headerSize);
if (requiredPacketSize > buf.length) {
byte[] newBuf = new byte[requiredPacketSize];
System.arraycopy(buf, 0, newBuf, 0, count);
buf = newBuf;
}

View File

@ -96,6 +96,10 @@ final class SSLConfiguration implements Cloneable {
static final boolean useCompatibilityMode = Utilities.getBooleanProperty(
"jdk.tls.client.useCompatibilityMode", true);
// Respond a close_notify alert if receiving close_notify alert.
static final boolean acknowledgeCloseNotify = Utilities.getBooleanProperty(
"jdk.tls.acknowledgeCloseNotify", false);
// TODO: Please remove after TLS 1.3 draft interop testing
// delete me
static int tls13VN;

View File

@ -155,6 +155,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
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);
@ -162,8 +163,9 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
HandshakeContext hc = conContext.handshakeContext;
HandshakeStatus hsStatus = null;
if (!conContext.isNegotiated &&
!conContext.isClosed() && !conContext.isBroken) {
if (!conContext.isNegotiated && !conContext.isBroken &&
!conContext.isInboundClosed() &&
!conContext.isOutboundClosed()) {
conContext.kickstart();
hsStatus = getHandshakeStatus();
@ -315,7 +317,8 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
}
// Is the sequence number is nearly overflow?
if (conContext.outputRecord.seqNumIsHuge()) {
if (conContext.outputRecord.seqNumIsHuge() ||
conContext.outputRecord.writeCipher.atKeyLimit()) {
hsStatus = tryKeyUpdate(hsStatus);
}
@ -343,25 +346,29 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
}
/**
* Try renegotiation or key update for sequence number wrap.
* 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 after the last record reading/writing process. As
* we request renegotiation or close the connection for wrapped sequence
* 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 the renegotiation or key update when the
// local is asking for it.
// Don't bother to kickstart if handshaking is in progress, or if the
// connection is not duplex-open.
if ((conContext.handshakeContext == null) &&
!conContext.isClosed() && !conContext.isBroken) {
!conContext.isOutboundClosed() &&
!conContext.isInboundClosed() &&
!conContext.isBroken) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest("key update to wrap sequence number");
SSLLogger.finest("trigger key update");
}
conContext.keyUpdate();
beginHandshake();
return conContext.getHandshakeStatus();
}
@ -471,8 +478,9 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
}
HandshakeStatus hsStatus = null;
if (!conContext.isNegotiated &&
!conContext.isClosed() && !conContext.isBroken) {
if (!conContext.isNegotiated && !conContext.isBroken &&
!conContext.isInboundClosed() &&
!conContext.isOutboundClosed()) {
conContext.kickstart();
/*
@ -677,7 +685,8 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
}
// Is the sequence number is nearly overflow?
if (conContext.inputRecord.seqNumIsHuge()) {
if (conContext.inputRecord.seqNumIsHuge() ||
conContext.inputRecord.readCipher.atKeyLimit()) {
pt.handshakeStatus =
tryKeyUpdate(pt.handshakeStatus);
}
@ -700,16 +709,42 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
@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)) {
conContext.fatal(Alert.INTERNAL_ERROR,
"closing inbound before receiving peer's close_notify");
}
conContext.closeInbound();
}
@Override
public synchronized boolean isInboundDone() {
return conContext.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();
}

View File

@ -34,8 +34,6 @@ import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLProtocolException;
import sun.security.ssl.SSLCipher.SSLReadCipher;
import sun.security.ssl.KeyUpdate.KeyUpdateMessage;
import sun.security.ssl.KeyUpdate.KeyUpdateRequest;
/**
* {@code InputRecord} implementation for {@code SSLEngine}.
@ -300,7 +298,7 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord {
handshakeBuffer.put(handshakeFrag);
handshakeBuffer.rewind();
break;
} if (remaining == handshakeMessageLen) {
} else if (remaining == handshakeMessageLen) {
if (handshakeHash.isHashable(handshakeType)) {
handshakeHash.receive(handshakeFrag);
}
@ -333,20 +331,6 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord {
return plaintexts.toArray(new Plaintext[0]);
}
// KeyLimit check during application data.
// atKeyLimit() inactive when limits not checked, tc set when limits
// are active.
if (readCipher.atKeyLimit()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("KeyUpdate: triggered, read side.");
}
PostHandshakeContext p = new PostHandshakeContext(tc);
KeyUpdate.handshakeProducer.produce(p,
new KeyUpdateMessage(p, KeyUpdateRequest.REQUESTED));
}
return new Plaintext[] {
new Plaintext(contentType,
majorVersion, minorVersion, -1, -1L, fragment)

View File

@ -31,8 +31,6 @@ import java.util.LinkedList;
import javax.net.ssl.SSLHandshakeException;
import sun.security.ssl.SSLCipher.SSLWriteCipher;
import sun.security.ssl.KeyUpdate.KeyUpdateMessage;
import sun.security.ssl.KeyUpdate.KeyUpdateRequest;
/**
* {@code OutputRecord} implementation for {@code SSLEngine}.
@ -43,7 +41,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord {
private boolean isTalkingToV2 = false; // SSLv2Hello
private ByteBuffer v2ClientHello = null; // SSLv2Hello
private boolean isCloseWaiting = false;
private volatile boolean isCloseWaiting = false;
SSLEngineOutputRecord(HandshakeHash handshakeHash) {
super(handshakeHash, SSLWriteCipher.nullTlsWriteCipher());
@ -63,8 +61,20 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord {
}
}
boolean isClosed() {
return isClosed || isCloseWaiting;
}
@Override
void encodeAlert(byte level, byte description) throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"alert message: " + Alert.nameOf(description));
}
return;
}
if (fragmenter == null) {
fragmenter = new HandshakeFragment();
}
@ -75,6 +85,14 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord {
@Override
void encodeHandshake(byte[] source,
int offset, int length) throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"handshake message",
ByteBuffer.wrap(source, offset, length));
}
return;
}
if (fragmenter == null) {
fragmenter = new HandshakeFragment();
@ -114,6 +132,14 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord {
@Override
void encodeChangeCipherSpec() throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"change_cipher_spec message");
}
return;
}
if (fragmenter == null) {
fragmenter = new HandshakeFragment();
}
@ -129,6 +155,23 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord {
Ciphertext encode(
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
if (isClosed) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"application data or cached messages");
}
return null;
} else if (isCloseWaiting) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"application data");
}
srcs = null; // use no application data.
}
return encode(srcs, srcsOffset, srcsLength, dsts[0]);
}
@ -248,25 +291,14 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord {
if (isFirstAppOutputRecord) {
isFirstAppOutputRecord = false;
}
// atKeyLimit() inactive when limits not checked, tc set when limits
// are active.
if (writeCipher.atKeyLimit()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("KeyUpdate: triggered, write side.");
}
PostHandshakeContext p = new PostHandshakeContext(tc);
KeyUpdate.handshakeProducer.produce(p,
new KeyUpdateMessage(p, KeyUpdateRequest.REQUESTED));
}
}
return new Ciphertext(ContentType.APPLICATION_DATA.id,
SSLHandshake.NOT_APPLICABLE.id, recordSN);
}
private Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException {
private Ciphertext acquireCiphertext(
ByteBuffer destination) throws IOException {
if (isTalkingToV2) { // SSLv2Hello
// We don't support SSLv2. Send an SSLv2 error message
// so that the connection can be closed gracefully.
@ -517,7 +549,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord {
boolean hasAlert() {
for (RecordMemo memo : handshakeMemos) {
if (memo.contentType == ContentType.ALERT.id) {
if (memo.contentType == ContentType.ALERT.id) {
return true;
}
}

View File

@ -366,6 +366,7 @@ enum SSLHandshake implements SSLConsumer, HandshakeProducer {
SSLHandshake(byte id, String name,
Map.Entry<SSLConsumer, ProtocolVersion[]>[] handshakeConsumers,
Map.Entry<HandshakeProducer, ProtocolVersion[]>[] handshakeProducers) {
this(id, name, handshakeConsumers, handshakeProducers,
(Map.Entry<HandshakeAbsence, ProtocolVersion[]>[])(
new Map.Entry[0]));
@ -375,6 +376,7 @@ enum SSLHandshake implements SSLConsumer, HandshakeProducer {
Map.Entry<SSLConsumer, ProtocolVersion[]>[] handshakeConsumers,
Map.Entry<HandshakeProducer, ProtocolVersion[]>[] handshakeProducers,
Map.Entry<HandshakeAbsence, ProtocolVersion[]>[] handshakeAbsence) {
this.id = id;
this.name = name;
this.handshakeConsumers = handshakeConsumers;
@ -404,7 +406,12 @@ enum SSLHandshake implements SSLConsumer, HandshakeProducer {
ProtocolVersion protocolVersion;
if ((hc.negotiatedProtocol == null) ||
(hc.negotiatedProtocol == ProtocolVersion.NONE)) {
protocolVersion = hc.maximumActiveProtocol;
if (hc.conContext.isNegotiated &&
hc.conContext.protocolVersion != ProtocolVersion.NONE) {
protocolVersion = hc.conContext.protocolVersion;
} else {
protocolVersion = hc.maximumActiveProtocol;
}
} else {
protocolVersion = hc.negotiatedProtocol;
}
@ -444,7 +451,12 @@ enum SSLHandshake implements SSLConsumer, HandshakeProducer {
ProtocolVersion protocolVersion;
if ((hc.negotiatedProtocol == null) ||
(hc.negotiatedProtocol == ProtocolVersion.NONE)) {
protocolVersion = hc.maximumActiveProtocol;
if (hc.conContext.isNegotiated &&
hc.conContext.protocolVersion != ProtocolVersion.NONE) {
protocolVersion = hc.conContext.protocolVersion;
} else {
protocolVersion = hc.maximumActiveProtocol;
}
} else {
protocolVersion = hc.negotiatedProtocol;
}

View File

@ -82,7 +82,7 @@ public final class SSLSocketImpl
private String peerHost;
private boolean autoClose;
private boolean isConnected = false;
private boolean tlsIsClosed = false;
private volatile boolean tlsIsClosed = false;
/*
* Is the local name service trustworthy?
@ -325,7 +325,7 @@ public final class SSLSocketImpl
}
@Override
public synchronized SSLSession getSession() {
public SSLSession getSession() {
try {
// start handshaking, if failed, the connection will be closed.
ensureNegotiated();
@ -343,7 +343,11 @@ public final class SSLSocketImpl
@Override
public synchronized SSLSession getHandshakeSession() {
if (conContext.handshakeContext != null) {
return conContext.handshakeContext.handshakeSession;
synchronized (this) {
if (conContext.handshakeContext != null) {
return conContext.handshakeContext.handshakeSession;
}
}
}
return null;
@ -370,23 +374,39 @@ public final class SSLSocketImpl
}
@Override
public synchronized void startHandshake() throws IOException {
checkWrite();
try {
conContext.kickstart();
public void startHandshake() throws IOException {
if (!isConnected) {
throw new SocketException("Socket is not connected");
}
// All initial handshaking goes through this operation until we
// have a valid SSL connection.
//
// Handle handshake messages only, need no application data.
if (!conContext.isNegotiated) {
readRecord();
if (conContext.isBroken || conContext.isInboundClosed() ||
conContext.isOutboundClosed()) {
throw new SocketException("Socket has been closed or broken");
}
synchronized (conContext) { // handshake lock
// double check the context status
if (conContext.isBroken || conContext.isInboundClosed() ||
conContext.isOutboundClosed()) {
throw new SocketException("Socket has been closed or broken");
}
try {
conContext.kickstart();
// All initial handshaking goes through this operation until we
// have a valid SSL connection.
//
// Handle handshake messages only, need no application data.
if (!conContext.isNegotiated) {
readHandshakeRecord();
}
} catch (IOException ioe) {
conContext.fatal(Alert.HANDSHAKE_FAILURE,
"Couldn't kickstart handshaking", ioe);
} catch (Exception oe) { // including RuntimeException
handleException(oe);
}
} catch (IOException ioe) {
conContext.fatal(Alert.HANDSHAKE_FAILURE,
"Couldn't kickstart handshaking", ioe);
} catch (Exception oe) { // including RuntimeException
handleException(oe);
}
}
@ -437,44 +457,264 @@ public final class SSLSocketImpl
}
@Override
public synchronized boolean isClosed() {
return tlsIsClosed && conContext.isClosed();
public boolean isClosed() {
return tlsIsClosed;
}
// Please don't synchronized this method. Otherwise, the read and close
// locks may be deadlocked.
@Override
public synchronized void close() throws IOException {
public void close() throws IOException {
if (tlsIsClosed) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("duplex close of SSLSocket");
}
try {
conContext.close();
// shutdown output bound, which may have been closed previously.
if (!isOutputShutdown()) {
duplexCloseOutput();
}
// shutdown input bound, which may have been closed previously.
if (!isInputShutdown()) {
duplexCloseInput();
}
if (!isClosed()) {
// close the connection directly
closeSocket(false);
}
} catch (IOException ioe) {
// ignore the exception
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("connection context closure failed", ioe);
SSLLogger.warning("SSLSocket duplex close failed", ioe);
}
} finally {
tlsIsClosed = true;
}
}
/**
* Duplex close, start from closing outbound.
*
* For TLS 1.2 [RFC 5246], unless some other fatal alert has been
* transmitted, each party is required to send a close_notify alert
* before closing the write side of the connection. The other party
* MUST respond with a close_notify alert of its own and close down
* the connection immediately, discarding any pending writes. It is
* not required for the initiator of the close to wait for the responding
* close_notify alert before closing the read side of the connection.
*
* For TLS 1.3, Each party MUST send a close_notify alert before
* closing its write side of the connection, unless it has already sent
* some error alert. This does not have any effect on its read side of
* the connection. Both parties need not wait to receive a close_notify
* alert before closing their read side of the connection, though doing
* so would introduce the possibility of truncation.
*
* In order to support user initiated duplex-close for TLS 1.3 connections,
* the user_canceled alert is used together with the close_notify alert.
*/
private void duplexCloseOutput() throws IOException {
boolean useUserCanceled = false;
boolean hasCloseReceipt = false;
if (conContext.isNegotiated) {
if (!conContext.protocolVersion.useTLS13PlusSpec()) {
hasCloseReceipt = true;
} else {
// Use a user_canceled alert for TLS 1.3 duplex close.
useUserCanceled = true;
}
} else if (conContext.handshakeContext != null) { // initial handshake
// Use user_canceled alert regardless the protocol versions.
useUserCanceled = true;
// The protocol version may have been negotiated.
ProtocolVersion pv = conContext.handshakeContext.negotiatedProtocol;
if (pv == null || (!pv.useTLS13PlusSpec())) {
hasCloseReceipt = true;
}
}
// Need a lock here so that the user_canceled alert and the
// close_notify alert can be delivered together.
try {
synchronized (conContext.outputRecord) {
// send a user_canceled alert if needed.
if (useUserCanceled) {
conContext.warning(Alert.USER_CANCELED);
}
// send a close_notify alert
conContext.warning(Alert.CLOSE_NOTIFY);
}
} finally {
if (!conContext.isOutboundClosed()) {
conContext.outputRecord.close();
}
if ((autoClose || !isLayered()) && !super.isOutputShutdown()) {
super.shutdownOutput();
}
}
if (!isInputShutdown()) {
bruteForceCloseInput(hasCloseReceipt);
}
}
/**
* Duplex close, start from closing inbound.
*
* This method should only be called when the outbound has been closed,
* but the inbound is still open.
*/
private void duplexCloseInput() throws IOException {
boolean hasCloseReceipt = false;
if (conContext.isNegotiated &&
!conContext.protocolVersion.useTLS13PlusSpec()) {
hasCloseReceipt = true;
} // No close receipt if handshake has no completed.
bruteForceCloseInput(hasCloseReceipt);
}
/**
* Brute force close the input bound.
*
* This method should only be called when the outbound has been closed,
* but the inbound is still open.
*/
private void bruteForceCloseInput(
boolean hasCloseReceipt) throws IOException {
if (hasCloseReceipt) {
// It is not required for the initiator of the close to wait for
// the responding close_notify alert before closing the read side
// of the connection. However, if the application protocol using
// TLS provides that any data may be carried over the underlying
// transport after the TLS connection is closed, the TLS
// implementation MUST receive a "close_notify" alert before
// indicating end-of-data to the application-layer.
try {
this.shutdown();
} finally {
if (!isInputShutdown()) {
shutdownInput(false);
}
}
} else {
if (!conContext.isInboundClosed()) {
conContext.inputRecord.close();
}
if ((autoClose || !isLayered()) && !super.isInputShutdown()) {
super.shutdownInput();
}
}
}
// Please don't synchronized this method. Otherwise, the read and close
// locks may be deadlocked.
@Override
public void shutdownInput() throws IOException {
shutdownInput(true);
}
// It is not required to check the close_notify receipt unless an
// application call shutdownInput() explicitly.
private void shutdownInput(
boolean checkCloseNotify) throws IOException {
if (isInputShutdown()) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("close inbound of SSLSocket");
}
// Is it ready to close inbound?
//
// No need to throw exception if the initial handshake is not started.
if (checkCloseNotify && !conContext.isInputCloseNotified &&
(conContext.isNegotiated || conContext.handshakeContext != null)) {
conContext.fatal(Alert.INTERNAL_ERROR,
"closing inbound before receiving peer's close_notify");
}
conContext.closeInbound();
if ((autoClose || !isLayered()) && !super.isInputShutdown()) {
super.shutdownInput();
}
}
@Override
public boolean isInputShutdown() {
return conContext.isInboundClosed() &&
((autoClose || !isLayered()) ? super.isInputShutdown(): true);
}
// Please don't synchronized this method. Otherwise, the read and close
// locks may be deadlocked.
@Override
public void shutdownOutput() throws IOException {
if (isOutputShutdown()) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("close outbound of SSLSocket");
}
conContext.closeOutbound();
if ((autoClose || !isLayered()) && !super.isOutputShutdown()) {
super.shutdownOutput();
}
}
@Override
public boolean isOutputShutdown() {
return conContext.isOutboundClosed() &&
((autoClose || !isLayered()) ? super.isOutputShutdown(): true);
}
@Override
public synchronized InputStream getInputStream() throws IOException {
if (isClosed() || conContext.isInboundDone()) {
throw new SocketException("Socket or inbound is closed");
if (isClosed()) {
throw new SocketException("Socket is closed");
}
if (!isConnected) {
throw new SocketException("Socket is not connected");
}
if (conContext.isInboundClosed() || isInputShutdown()) {
throw new SocketException("Socket input is already shutdown");
}
return appInput;
}
private synchronized void ensureNegotiated() throws IOException {
if (conContext.isNegotiated ||
conContext.isClosed() || conContext.isBroken) {
private void ensureNegotiated() throws IOException {
if (conContext.isNegotiated || conContext.isBroken ||
conContext.isInboundClosed() || conContext.isOutboundClosed()) {
return;
}
startHandshake();
synchronized (conContext) { // handshake lock
// double check the context status
if (conContext.isNegotiated || conContext.isBroken ||
conContext.isInboundClosed() ||
conContext.isOutboundClosed()) {
return;
}
startHandshake();
}
}
/**
@ -489,7 +729,7 @@ public final class SSLSocketImpl
private ByteBuffer buffer;
// Is application data available in the stream?
private boolean appDataIsAvailable;
private volatile boolean appDataIsAvailable;
AppInputStream() {
this.appDataIsAvailable = false;
@ -514,7 +754,7 @@ public final class SSLSocketImpl
* Read a single byte, returning -1 on non-fault EOF status.
*/
@Override
public synchronized int read() throws IOException {
public int read() throws IOException {
int n = read(oneByte, 0, 1);
if (n <= 0) { // EOF
return -1;
@ -536,7 +776,7 @@ public final class SSLSocketImpl
* and returning "-1" on non-fault EOF status.
*/
@Override
public synchronized int read(byte[] b, int off, int len)
public int read(byte[] b, int off, int len)
throws IOException {
if (b == null) {
throw new NullPointerException("the target buffer is null");
@ -553,75 +793,54 @@ public final class SSLSocketImpl
}
// start handshaking if the connection has not been negotiated.
if (!conContext.isNegotiated &&
!conContext.isClosed() && !conContext.isBroken) {
if (!conContext.isNegotiated && !conContext.isBroken &&
!conContext.isInboundClosed() &&
!conContext.isOutboundClosed()) {
ensureNegotiated();
}
// Read the available bytes at first.
int remains = available();
if (remains > 0) {
int howmany = Math.min(remains, len);
buffer.get(b, off, howmany);
return howmany;
// Check if the Socket is invalid (error or closed).
if (!conContext.isNegotiated ||
conContext.isBroken || conContext.isInboundClosed()) {
throw new SocketException("Connection or inbound has closed");
}
appDataIsAvailable = false;
int volume = 0;
try {
/*
* Read data if needed ... notice that the connection
* guarantees that handshake, alert, and change cipher spec
* data streams are handled as they arrive, so we never
* see them here.
*/
while (volume == 0) {
// Clear the buffer for a new record reading.
buffer.clear();
// Read the available bytes at first.
//
// Note that the receiving and processing of post-handshake message
// are also synchronized with the read lock.
synchronized (this) {
int remains = available();
if (remains > 0) {
int howmany = Math.min(remains, len);
buffer.get(b, off, howmany);
// grow the buffer if needed
int inLen = conContext.inputRecord.bytesInCompletePacket();
if (inLen < 0) { // EOF
handleEOF(null);
// if no exception thrown
return -1;
}
// Is this packet bigger than SSL/TLS normally allows?
if (inLen > SSLRecord.maxLargeRecordSize) {
throw new SSLProtocolException(
"Illegal packet size: " + inLen);
}
if (inLen > buffer.remaining()) {
buffer = ByteBuffer.allocate(inLen);
}
volume = readRecord(buffer);
buffer.flip();
if (volume < 0) { // EOF
// treat like receiving a close_notify warning message.
conContext.isInputCloseNotified = true;
conContext.closeInbound();
return -1;
} else if (volume > 0) {
appDataIsAvailable = true;
break;
}
return howmany;
}
// file the destination buffer
int howmany = Math.min(len, volume);
buffer.get(b, off, howmany);
return howmany;
} catch (Exception e) { // including RuntimeException
// shutdown and rethrow (wrapped) exception as appropriate
handleException(e);
appDataIsAvailable = false;
try {
ByteBuffer bb = readApplicationRecord(buffer);
if (bb == null) { // EOF
return -1;
} else {
// The buffer may be reallocated for bigger capacity.
buffer = bb;
}
// dummy for compiler
return -1;
bb.flip();
int volume = Math.min(len, bb.remaining());
buffer.get(b, off, volume);
appDataIsAvailable = true;
return volume;
} catch (Exception e) { // including RuntimeException
// shutdown and rethrow (wrapped) exception as appropriate
handleException(e);
// dummy for compiler
return -1;
}
}
}
@ -658,20 +877,53 @@ public final class SSLSocketImpl
SSLLogger.finest("Closing input stream");
}
conContext.closeInbound();
try {
shutdownInput(false);
} catch (IOException ioe) {
// ignore the exception
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("input stream close failed", ioe);
}
}
}
/**
* Return whether we have reached end-of-file.
*
* If the socket is not connected, has been shutdown because of an error
* or has been closed, throw an Exception.
*/
private boolean checkEOF() throws IOException {
if (conContext.isInboundClosed()) {
return true;
} else if (conContext.isInputCloseNotified || conContext.isBroken) {
if (conContext.closeReason == null) {
return true;
} else {
throw new SSLException(
"Connection has closed: " + conContext.closeReason,
conContext.closeReason);
}
}
return false;
}
}
@Override
public synchronized OutputStream getOutputStream() throws IOException {
if (isClosed() || conContext.isOutboundDone()) {
throw new SocketException("Socket or outbound is closed");
if (isClosed()) {
throw new SocketException("Socket is closed");
}
if (!isConnected) {
throw new SocketException("Socket is not connected");
}
if (conContext.isOutboundDone() || isOutputShutdown()) {
throw new SocketException("Socket output is already shutdown");
}
return appOutput;
}
@ -691,7 +943,7 @@ public final class SSLSocketImpl
}
@Override
public synchronized void write(byte[] b,
public void write(byte[] b,
int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException("the source buffer is null");
@ -700,25 +952,47 @@ public final class SSLSocketImpl
"buffer length: " + b.length + ", offset; " + off +
", bytes to read:" + len);
} else if (len == 0) {
//
// Don't bother to really write empty records. We went this
// far to drive the handshake machinery, for correctness; not
// writing empty records improves performance by cutting CPU
// time and network resource usage. However, some protocol
// implementations are fragile and don't like to see empty
// records, so this also increases robustness.
//
return;
}
// start handshaking if the connection has not been negotiated.
if (!conContext.isNegotiated &&
!conContext.isClosed() && !conContext.isBroken) {
// Start handshaking if the connection has not been negotiated.
if (!conContext.isNegotiated && !conContext.isBroken &&
!conContext.isInboundClosed() &&
!conContext.isOutboundClosed()) {
ensureNegotiated();
}
// check if the Socket is invalid (error or closed)
checkWrite();
// Check if the Socket is invalid (error or closed).
if (!conContext.isNegotiated ||
conContext.isBroken || conContext.isOutboundClosed()) {
throw new SocketException("Connection or outbound has closed");
}
//
// Delegate the writing to the underlying socket.
try {
writeRecord(b, off, len);
checkWrite();
} catch (IOException ioe) {
// shutdown and rethrow (wrapped) exception as appropriate
handleException(ioe);
conContext.outputRecord.deliver(b, off, len);
} catch (SSLHandshakeException she) {
// may be record sequence number overflow
conContext.fatal(Alert.HANDSHAKE_FAILURE, she);
} catch (IOException e) {
conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
}
// Is the sequence number is nearly overflow, or has the key usage
// limit been reached?
if (conContext.outputRecord.seqNumIsHuge() ||
conContext.outputRecord.writeCipher.atKeyLimit()) {
tryKeyUpdate();
}
}
@ -728,7 +1002,14 @@ public final class SSLSocketImpl
SSLLogger.finest("Closing output stream");
}
conContext.closeOutbound();
try {
shutdownOutput();
} catch (IOException ioe) {
// ignore the exception
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("output stream close failed", ioe);
}
}
}
}
@ -773,39 +1054,11 @@ public final class SSLSocketImpl
return conContext.sslConfig.socketAPSelector;
}
private synchronized void writeRecord(byte[] source,
int offset, int length) throws IOException {
if (conContext.isOutboundDone()) {
throw new SocketException("Socket or outbound closed");
}
//
// Don't bother to really write empty records. We went this
// far to drive the handshake machinery, for correctness; not
// writing empty records improves performance by cutting CPU
// time and network resource usage. However, some protocol
// implementations are fragile and don't like to see empty
// records, so this also increases robustness.
//
if (length > 0) {
try {
conContext.outputRecord.deliver(source, offset, length);
} catch (SSLHandshakeException she) {
// may be record sequence number overflow
conContext.fatal(Alert.HANDSHAKE_FAILURE, she);
} catch (IOException e) {
conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
}
}
// Is the sequence number is nearly overflow?
if (conContext.outputRecord.seqNumIsHuge()) {
tryKeyUpdate();
}
}
private synchronized int readRecord() throws IOException {
while (!conContext.isInboundDone()) {
/**
* Read the initial handshake records.
*/
private int readHandshakeRecord() throws IOException {
while (!conContext.isInboundClosed()) {
try {
Plaintext plainText = decode(null);
if ((plainText.contentType == ContentType.HANDSHAKE.id) &&
@ -816,7 +1069,7 @@ public final class SSLSocketImpl
throw ssle;
} catch (IOException ioe) {
if (!(ioe instanceof SSLException)) {
throw new SSLException("readRecord", ioe);
throw new SSLException("readHandshakeRecord", ioe);
} else {
throw ioe;
}
@ -826,8 +1079,20 @@ public final class SSLSocketImpl
return -1;
}
private synchronized int readRecord(ByteBuffer buffer) throws IOException {
while (!conContext.isInboundDone()) {
/**
* Read application data record. Used by AppInputStream only, but defined
* here so as to use the socket level synchronization.
*
* Note that the connection guarantees that handshake, alert, and change
* cipher spec data streams are handled as they arrive, so we never see
* them here.
*
* Note: Please be careful about the synchronization, and don't use this
* method other than in the AppInputStream class!
*/
private ByteBuffer readApplicationRecord(
ByteBuffer buffer) throws IOException {
while (!conContext.isInboundClosed()) {
/*
* clean the buffer and check if it is too small, e.g. because
* the AppInputStream did not have the chance to see the
@ -841,23 +1106,33 @@ public final class SSLSocketImpl
handleEOF(null);
// if no exception thrown
return -1;
return null;
}
if (buffer.remaining() < inLen) {
return 0;
// Is this packet bigger than SSL/TLS normally allows?
if (inLen > SSLRecord.maxLargeRecordSize) {
throw new SSLProtocolException(
"Illegal packet size: " + inLen);
}
if (inLen > buffer.remaining()) {
buffer = ByteBuffer.allocate(inLen);
}
try {
Plaintext plainText = decode(buffer);
if (plainText.contentType == ContentType.APPLICATION_DATA.id) {
return buffer.position();
Plaintext plainText;
synchronized (this) {
plainText = decode(buffer);
}
if (plainText.contentType == ContentType.APPLICATION_DATA.id &&
buffer.position() > 0) {
return buffer;
}
} catch (SSLException ssle) {
throw ssle;
} catch (IOException ioe) {
if (!(ioe instanceof SSLException)) {
throw new SSLException("readRecord", ioe);
throw new SSLException("readApplicationRecord", ioe);
} else {
throw ioe;
}
@ -867,7 +1142,7 @@ public final class SSLSocketImpl
//
// couldn't read, due to some kind of error
//
return -1;
return null;
}
private Plaintext decode(ByteBuffer destination) throws IOException {
@ -887,7 +1162,8 @@ public final class SSLSocketImpl
// Is the sequence number is nearly overflow?
if (plainText != Plaintext.PLAINTEXT_NULL &&
conContext.inputRecord.seqNumIsHuge()) {
(conContext.inputRecord.seqNumIsHuge() ||
conContext.inputRecord.readCipher.atKeyLimit())) {
tryKeyUpdate();
}
@ -895,69 +1171,28 @@ public final class SSLSocketImpl
}
/**
* Try renegotiation or key update for sequence number wrap.
* 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 after the last record reading/writing process. As
* we request renegotiation or close the connection for wrapped sequence
* 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 void tryKeyUpdate() throws IOException {
// Don't bother to kickstart the renegotiation or key update when the
// local is asking for it.
// Don't bother to kickstart if handshaking is in progress, or if the
// connection is not duplex-open.
if ((conContext.handshakeContext == null) &&
!conContext.isClosed() && !conContext.isBroken) {
!conContext.isOutboundClosed() &&
!conContext.isInboundClosed() &&
!conContext.isBroken) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest("key update to wrap sequence number");
}
conContext.keyUpdate();
}
}
private void closeSocket(boolean selfInitiated) throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("close the ssl connection " +
(selfInitiated ? "(initiative)" : "(passive)"));
}
if (autoClose || !isLayered()) {
super.close();
} else if (selfInitiated) {
// wait for close_notify alert to clear input stream.
waitForClose();
}
}
/**
* Wait for close_notify alert for a graceful closure.
*
* [RFC 5246] If the application protocol using TLS provides that any
* data may be carried over the underlying transport after the TLS
* connection is closed, the TLS implementation must receive the responding
* close_notify alert before indicating to the application layer that
* the TLS connection has ended. If the application protocol will not
* transfer any additional data, but will only close the underlying
* transport connection, then the implementation MAY choose to close the
* transport without waiting for the responding close_notify.
*/
private void waitForClose() throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("wait for close_notify or alert");
}
while (!conContext.isInboundDone()) {
try {
Plaintext plainText = decode(null);
// discard and continue
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest(
"discard plaintext while waiting for close", plainText);
}
} catch (Exception e) { // including RuntimeException
handleException(e);
SSLLogger.finest("trigger key update");
}
startHandshake();
}
}
@ -1040,41 +1275,6 @@ public final class SSLSocketImpl
conContext.sslConfig.serverNames, host);
}
/**
* Return whether we have reached end-of-file.
*
* If the socket is not connected, has been shutdown because of an error
* or has been closed, throw an Exception.
*/
synchronized boolean checkEOF() throws IOException {
if (conContext.isClosed()) {
return true;
} else if (conContext.isInputCloseNotified || conContext.isBroken) {
if (conContext.closeReason == null) {
return true;
} else {
throw new SSLException(
"Connection has been shutdown: " + conContext.closeReason,
conContext.closeReason);
}
}
return false;
}
/**
* Check if we can write data to this socket.
*/
synchronized void checkWrite() throws IOException {
if (checkEOF() || conContext.isOutboundClosed()) {
// we are at EOF, write must throw Exception
throw new SocketException("Connection closed");
}
if (!isConnected) {
throw new SocketException("Socket is not connected");
}
}
/**
* Handle an exception.
*
@ -1132,7 +1332,7 @@ public final class SSLSocketImpl
} else {
// treat as if we had received a close_notify
conContext.isInputCloseNotified = true;
conContext.transport.shutdown();
shutdownInput();
return Plaintext.PLAINTEXT_NULL;
}
@ -1174,4 +1374,51 @@ public final class SSLSocketImpl
}
}
}
private void closeSocket(boolean selfInitiated) throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("close the SSL connection " +
(selfInitiated ? "(initiative)" : "(passive)"));
}
if (autoClose || !isLayered()) {
super.close();
} else if (selfInitiated) {
if (!conContext.isInboundClosed() && !isInputShutdown()) {
// wait for close_notify alert to clear input stream.
waitForClose();
}
}
}
/**
* Wait for close_notify alert for a graceful closure.
*
* [RFC 5246] If the application protocol using TLS provides that any
* data may be carried over the underlying transport after the TLS
* connection is closed, the TLS implementation must receive the responding
* close_notify alert before indicating to the application layer that
* the TLS connection has ended. If the application protocol will not
* transfer any additional data, but will only close the underlying
* transport connection, then the implementation MAY choose to close the
* transport without waiting for the responding close_notify.
*/
private void waitForClose() throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("wait for close_notify or alert");
}
while (!conContext.isInboundClosed()) {
try {
Plaintext plainText = decode(null);
// discard and continue
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest(
"discard plaintext while waiting for close", plainText);
}
} catch (Exception e) { // including RuntimeException
handleException(e);
}
}
}
}

View File

@ -38,8 +38,6 @@ import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLProtocolException;
import sun.security.ssl.SSLCipher.SSLReadCipher;
import sun.security.ssl.KeyUpdate.KeyUpdateMessage;
import sun.security.ssl.KeyUpdate.KeyUpdateRequest;
/**
* {@code InputRecord} implementation for {@code SSLSocket}.
@ -348,20 +346,6 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
return plaintexts.toArray(new Plaintext[0]);
}
// KeyLimit check during application data.
// atKeyLimit() inactive when limits not checked, tc set when limits
// are active.
if (readCipher.atKeyLimit()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("KeyUpdate: triggered, read side.");
}
PostHandshakeContext p = new PostHandshakeContext(tc);
KeyUpdate.handshakeProducer.produce(p,
new KeyUpdateMessage(p, KeyUpdateRequest.REQUESTED));
}
return new Plaintext[] {
new Plaintext(contentType,
majorVersion, minorVersion, -1, -1L, fragment)

View File

@ -28,12 +28,10 @@ package sun.security.ssl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketException;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLHandshakeException;
import sun.security.ssl.KeyUpdate.KeyUpdateMessage;
import sun.security.ssl.KeyUpdate.KeyUpdateRequest;
/**
* {@code OutputRecord} implementation for {@code SSLSocket}.
*/
@ -53,7 +51,16 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
}
@Override
void encodeAlert(byte level, byte description) throws IOException {
synchronized void encodeAlert(
byte level, byte description) throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"alert message: " + Alert.nameOf(description));
}
return;
}
// use the buf of ByteArrayOutputStream
int position = headerSize + writeCipher.getExplicitNonceSize();
count = position;
@ -63,6 +70,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine("WRITE: " + protocolVersion +
" " + ContentType.ALERT.name +
"(" + Alert.nameOf(description) + ")" +
", length = " + (count - headerSize));
}
@ -83,8 +91,17 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
}
@Override
void encodeHandshake(byte[] source,
synchronized void encodeHandshake(byte[] source,
int offset, int length) throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"handshake message",
ByteBuffer.wrap(source, offset, length));
}
return;
}
if (firstMessage) {
firstMessage = false;
@ -182,7 +199,14 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
}
@Override
void encodeChangeCipherSpec() throws IOException {
synchronized void encodeChangeCipherSpec() throws IOException {
if (isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("outbound has closed, ignore outbound " +
"change_cipher_spec message");
}
return;
}
// use the buf of ByteArrayOutputStream
int position = headerSize + writeCipher.getExplicitNonceSize();
@ -207,7 +231,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
}
@Override
public void flush() throws IOException {
public synchronized void flush() throws IOException {
int position = headerSize + writeCipher.getExplicitNonceSize();
if (count <= position) {
return;
@ -237,7 +261,12 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
}
@Override
void deliver(byte[] source, int offset, int length) throws IOException {
synchronized void deliver(
byte[] source, int offset, int length) throws IOException {
if (isClosed()) {
throw new SocketException("Connection or outbound has been closed");
}
if (writeCipher.authenticator.seqNumOverflow()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine(
@ -304,23 +333,11 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
}
offset += fragLen;
// atKeyLimit() inactive when limits not checked, tc set when limits
// are active.
if (writeCipher.atKeyLimit()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("KeyUpdate: triggered, write side.");
}
PostHandshakeContext p = new PostHandshakeContext(tc);
KeyUpdate.handshakeProducer.produce(p,
new KeyUpdateMessage(p, KeyUpdateRequest.REQUESTED));
}
}
}
@Override
void setDeliverStream(OutputStream outputStream) {
synchronized void setDeliverStream(OutputStream outputStream) {
this.deliverStream = outputStream;
}
@ -347,7 +364,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
* This avoids issues in the outbound direction. For a full fix,
* the peer must have similar protections.
*/
boolean needToSplitPayload() {
private boolean needToSplitPayload() {
return (!protocolVersion.useTLS11PlusSpec()) &&
writeCipher.isCBCMode() && !isFirstAppOutputRecord &&
Record.enableCBCProtection;

View File

@ -25,7 +25,6 @@
package sun.security.ssl;
import java.io.Closeable;
import java.io.IOException;
import java.security.AccessControlContext;
import java.security.AccessController;
@ -45,7 +44,7 @@ import sun.security.ssl.SupportedGroupsExtension.NamedGroup;
/**
* SSL/(D)TLS transportation context.
*/
class TransportContext implements ConnectionContext, Closeable {
class TransportContext implements ConnectionContext {
final SSLTransport transport;
// registered plaintext consumers
@ -62,7 +61,7 @@ class TransportContext implements ConnectionContext, Closeable {
boolean isNegotiated = false;
boolean isBroken = false;
boolean isInputCloseNotified = false;
boolean isOutputCloseNotified = false;
boolean peerUserCanceled = false;
Exception closeReason = null;
// negotiated security parameters
@ -229,10 +228,6 @@ class TransportContext implements ConnectionContext, Closeable {
}
}
void keyUpdate() throws IOException {
kickstart();
}
boolean isPostHandshakeContext() {
return handshakeContext != null &&
(handshakeContext instanceof PostHandshakeContext);
@ -348,7 +343,7 @@ class TransportContext implements ConnectionContext, Closeable {
//
// If we haven't even started handshaking yet, or we are the recipient
// of a fatal alert, no need to generate a fatal close alert.
if (!recvFatalAlert && !isOutboundDone() && !isBroken &&
if (!recvFatalAlert && !isOutboundClosed() && !isBroken &&
(isNegotiated || handshakeContext != null)) {
try {
outputRecord.encodeAlert(Alert.Level.FATAL.level, alert.id);
@ -436,35 +431,26 @@ class TransportContext implements ConnectionContext, Closeable {
return outputRecord.isClosed();
}
boolean isInboundDone() {
boolean isInboundClosed() {
return inputRecord.isClosed();
}
boolean isClosed() {
return isOutboundClosed() && isInboundDone();
}
@Override
public void close() throws IOException {
if (!isOutboundDone()) {
closeOutbound();
}
if (!isInboundDone()) {
closeInbound();
}
}
void closeInbound() {
if (isInboundDone()) {
// Close inbound, no more data should be delivered to the underlying
// transportation connection.
void closeInbound() throws SSLException {
if (isInboundClosed()) {
return;
}
try {
if (isInputCloseNotified) { // passive close
passiveInboundClose();
} else { // initiative close
// Important note: check if the initial handshake is started at
// first so that the passiveInboundClose() implementation need not
// to consider the case any more.
if (!isInputCloseNotified) {
// the initial handshake is not started
initiateInboundClose();
} else {
passiveInboundClose();
}
} catch (IOException ioe) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
@ -473,8 +459,58 @@ class TransportContext implements ConnectionContext, Closeable {
}
}
// Close the connection passively. The closure could be kickoff by
// receiving a close_notify alert or reaching end_of_file of the socket.
//
// Note that this method is called only if the initial handshake has
// started or completed.
private void passiveInboundClose() throws IOException {
if (!isInboundClosed()) {
inputRecord.close();
}
// For TLS 1.2 and prior version, it is required to respond with
// a close_notify alert of its own and close down the connection
// immediately, discarding any pending writes.
if (!isOutboundClosed()) {
boolean needCloseNotify = SSLConfiguration.acknowledgeCloseNotify;
if (!needCloseNotify) {
if (isNegotiated) {
if (!protocolVersion.useTLS13PlusSpec()) {
needCloseNotify = true;
}
} else if (handshakeContext != null) { // initial handshake
ProtocolVersion pv = handshakeContext.negotiatedProtocol;
if (pv == null || (!pv.useTLS13PlusSpec())) {
needCloseNotify = true;
}
}
}
if (needCloseNotify) {
synchronized (outputRecord) {
try {
// send a close_notify alert
warning(Alert.CLOSE_NOTIFY);
} finally {
outputRecord.close();
}
}
}
}
}
// Initiate a inbound close when the handshake is not started.
private void initiateInboundClose() throws IOException {
if (!isInboundClosed()) {
inputRecord.close();
}
}
// Close outbound, no more data should be received from the underlying
// transportation connection.
void closeOutbound() {
if (isOutboundDone()) {
if (isOutboundClosed()) {
return;
}
@ -487,89 +523,29 @@ class TransportContext implements ConnectionContext, Closeable {
}
}
// Close the connection passively. The closure could be kickoff by
// receiving a close_notify alert or reaching end_of_file of the socket.
private void passiveInboundClose() throws IOException {
if (!isInboundDone()) {
inputRecord.close();
}
// For TLS 1.2 and prior version, it is required to respond with
// a close_notify alert of its own and close down the connection
// immediately, discarding any pending writes.
if (!isOutboundDone() && !isOutputCloseNotified) {
try {
// send a close_notify alert
warning(Alert.CLOSE_NOTIFY);
} finally {
// any data received after a closure alert is ignored.
isOutputCloseNotified = true;
outputRecord.close();
}
}
transport.shutdown();
}
// Initiate a close by sending a close_notify alert.
private void initiateInboundClose() throws IOException {
// TLS 1.3 does not define how to initiate and close a TLS connection
// gracefully. We will always send a close_notify alert, and close
// the underlying transportation layer if needed.
if (!isInboundDone() && !isInputCloseNotified) {
try {
// send a close_notify alert
warning(Alert.CLOSE_NOTIFY);
} finally {
// any data received after a closure alert is ignored.
isInputCloseNotified = true;
inputRecord.close();
}
}
// For TLS 1.3, input closure is independent from output closure. Both
// parties need not wait to receive a "close_notify" alert before
// closing their read side of the connection.
//
// For TLS 1.2 and prior version, it is not required for the initiator
// of the close to wait for the responding close_notify alert before
// closing the read side of the connection.
try {
transport.shutdown();
} finally {
if (!isOutboundDone()) {
outputRecord.close();
}
}
}
// Initiate a close by sending a close_notify alert.
private void initiateOutboundClose() throws IOException {
if (!isOutboundDone() && !isOutputCloseNotified) {
try { // close outputRecord
boolean useUserCanceled = false;
if (!isNegotiated && (handshakeContext != null) && !peerUserCanceled) {
// initial handshake
useUserCanceled = true;
}
// Need a lock here so that the user_canceled alert and the
// close_notify alert can be delivered together.
synchronized (outputRecord) {
try {
// send a user_canceled alert if needed.
if (useUserCanceled) {
warning(Alert.USER_CANCELED);
}
// send a close_notify alert
warning(Alert.CLOSE_NOTIFY);
} finally {
// any data received after a closure alert is ignored.
isOutputCloseNotified = true;
outputRecord.close();
}
}
// It is not required for the initiator of the close to wait for the
// responding close_notify alert before closing the read side of the
// connection. However, if the application protocol using TLS
// provides that any data may be carried over the underlying transport
// after the TLS connection is closed, the TLS implementation MUST
// receive a "close_notify" alert before indicating end-of-data to the
// application-layer.
try {
transport.shutdown();
} finally {
if (!isInboundDone()) {
inputRecord.close();
}
}
}
// Note; HandshakeStatus.FINISHED status is retrieved in other places.
@ -578,25 +554,28 @@ class TransportContext implements ConnectionContext, Closeable {
// If no handshaking, special case to wrap alters or
// post-handshake messages.
return HandshakeStatus.NEED_WRAP;
} else if (isOutboundClosed() && isInboundClosed()) {
return HandshakeStatus.NOT_HANDSHAKING;
} else if (handshakeContext != null) {
if (!handshakeContext.delegatedActions.isEmpty()) {
return HandshakeStatus.NEED_TASK;
} else if (sslContext.isDTLS() &&
!inputRecord.isEmpty()) {
return HandshakeStatus.NEED_UNWRAP_AGAIN;
} else {
return HandshakeStatus.NEED_UNWRAP;
} else if (!isInboundClosed()) {
if (sslContext.isDTLS() &&
!inputRecord.isEmpty()) {
return HandshakeStatus.NEED_UNWRAP_AGAIN;
} else {
return HandshakeStatus.NEED_UNWRAP;
}
} else if (!isOutboundClosed()) {
// Special case that the inbound was closed, but outbound open.
return HandshakeStatus.NEED_WRAP;
}
} else if (isOutboundDone() && !isInboundDone()) {
/*
* Special case where we're closing, but
* still need the close_notify before we
* can officially be closed.
*
* Note isOutboundDone is taken care of by
* hasOutboundData() above.
*/
} else if (isOutboundClosed() && !isInboundClosed()) {
// Special case that the outbound was closed, but inbound open.
return HandshakeStatus.NEED_UNWRAP;
} else if (!isOutboundClosed() && isInboundClosed()) {
// Special case that the inbound was closed, but outbound open.
return HandshakeStatus.NEED_WRAP;
}
return HandshakeStatus.NOT_HANDSHAKING;
@ -607,8 +586,10 @@ class TransportContext implements ConnectionContext, Closeable {
outputRecord.tc = this;
inputRecord.tc = this;
cipherSuite = handshakeContext.negotiatedCipherSuite;
inputRecord.readCipher.baseSecret = handshakeContext.baseReadSecret;
outputRecord.writeCipher.baseSecret = handshakeContext.baseWriteSecret;
inputRecord.readCipher.baseSecret =
handshakeContext.baseReadSecret;
outputRecord.writeCipher.baseSecret =
handshakeContext.baseWriteSecret;
}
handshakeContext = null;

View File

@ -35,6 +35,7 @@
* @build Http2TestServer
* @build jdk.testlibrary.SimpleSSLContext
* @run testng/othervm
* -Djdk.tls.acknowledgeCloseNotify=true
* -Djdk.httpclient.HttpClient.log=trace,headers,requests
* CookieHeaderTest
*/

View File

@ -33,6 +33,7 @@
* java.net.http/jdk.internal.net.http.frame
* java.net.http/jdk.internal.net.http.hpack
* @run testng/othervm
* -Djdk.tls.acknowledgeCloseNotify=true
* -Djdk.internal.httpclient.debug=true
* -Djdk.httpclient.HttpClient.log=headers,errors EncodedCharsInURI
*/

View File

@ -31,7 +31,7 @@
* java.net.http/jdk.internal.net.http.common
* java.net.http/jdk.internal.net.http.frame
* java.net.http/jdk.internal.net.http.hpack
* @run testng/othervm ServerCloseTest
* @run testng/othervm -Djdk.tls.acknowledgeCloseNotify=true ServerCloseTest
*/
//* -Djdk.internal.httpclient.debug=true

View File

@ -191,6 +191,7 @@ public class SSLEngineAlpnTest {
if (debug) {
System.setProperty("javax.net.debug", "all");
}
System.setProperty("jdk.tls.acknowledgeCloseNotify", "true");
System.out.println("Test args: " + Arrays.toString(args));
// Validate parameters
@ -358,6 +359,7 @@ public class SSLEngineAlpnTest {
log("\tClosing clientEngine's *OUTBOUND*...");
clientEngine.closeOutbound();
// serverEngine.closeOutbound();
dataDone = true;
}
}

View File

@ -25,7 +25,14 @@
* @test
* @bug 5019096
* @summary Add scatter/gather APIs for SSLEngine
*
* @run main/othervm Arrays SSL
* @run main/othervm Arrays TLS
* @run main/othervm Arrays SSLv3
* @run main/othervm Arrays TLSv1
* @run main/othervm Arrays TLSv1.1
* @run main/othervm Arrays TLSv1.2
* @run main/othervm Arrays TLSv1.3
* @run main/othervm -Djdk.tls.acknowledgeCloseNotify=true Arrays TLSv1.3
*/
import javax.net.ssl.*;
@ -37,6 +44,8 @@ import java.nio.*;
public class Arrays {
private static boolean debug = false;
private static boolean acknowledgeCloseNotify =
"true".equals(System.getProperty("jdk.tls.acknowledgeCloseNotify"));
private SSLContext sslc;
private SSLEngine ssle1; // client
@ -131,11 +140,13 @@ public class Arrays {
for (int i = 0; i < appOutArray1.length; i++) {
if (appOutArray1[i].remaining() != 0) {
log("1st out not done");
done = false;
}
}
if (appOut2.remaining() != 0) {
log("2nd out not done");
done = false;
}
@ -145,6 +156,19 @@ public class Arrays {
appOutArray1[i].rewind();
}
ssle1.closeOutbound();
String protocol = ssle2.getSession().getProtocol();
if (!acknowledgeCloseNotify) {
switch (ssle2.getSession().getProtocol()) {
case "SSLv3":
case "TLSv1":
case "TLSv1.1":
case "TLSv1.2":
break;
default: // TLSv1.3
// TLS 1.3, half-close only.
ssle2.closeOutbound();
}
}
dataDone = true;
}
}
@ -155,7 +179,9 @@ public class Arrays {
checkTransfer(appInArray1, appOut2);
}
private static String contextVersion;
public static void main(String args[]) throws Exception {
contextVersion = args[0];
Arrays test;
@ -165,7 +191,7 @@ public class Arrays {
test.runTest();
System.out.println("Test Passed.");
System.err.println("Test Passed.");
}
/*
@ -198,7 +224,7 @@ public class Arrays {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);
SSLContext sslCtx = SSLContext.getInstance("TLS");
SSLContext sslCtx = SSLContext.getInstance(contextVersion);
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
@ -288,7 +314,7 @@ public class Arrays {
private static void log(String str) {
if (debug) {
System.out.println(str);
System.err.println(str);
}
}
}

View File

@ -26,6 +26,8 @@
* @bug 4981697
* @summary Rework the X509KeyManager to avoid incompatibility issues
* @author Brad R. Wetmore
*
* @run main/othervm -Djdk.tls.acknowledgeCloseNotify=true ExtendedKeyEngine
*/
import javax.net.ssl.*;

View File

@ -936,6 +936,7 @@ abstract public class SSLEngineTestCase {
case SUPPORTED_NON_KRB_NON_SHA_CIPHERS:
case SUPPORTED_KRB_CIPHERS:
case ENABLED_NON_KRB_NOT_ANON_CIPHERS:
case TLS13_CIPHERS:
if (error != null) {
System.out.println("Test Failed: " + cs);
System.err.println("Test Exception for " + cs);

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 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.
*
* 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.
*/
/*
* @test
* @bug 8207009
* @summary Testing TLS engines closing using each of the supported
* cipher suites.
* @library /sun/security/krb5/auto /javax/net/ssl/TLSCommon
* @modules java.security.jgss
* jdk.security.auth
* java.security.jgss/sun.security.jgss.krb5
* java.security.jgss/sun.security.krb5:+open
* java.security.jgss/sun.security.krb5.internal:+open
* java.security.jgss/sun.security.krb5.internal.ccache
* java.security.jgss/sun.security.krb5.internal.crypto
* java.security.jgss/sun.security.krb5.internal.ktab
* java.base/sun.security.util
* @run main/othervm -Dtest.security.protocol=TLSv1.2
* -Dtest.mode=norm TLSEnginesClosureTest
* @run main/othervm -Dtest.security.protocol=TLSv1.2
* -Dtest.mode=norm_sni TLSEnginesClosureTest
* @run main/othervm -Dtest.security.protocol=TLSv1.2
* -Dtest.mode=krb TLSEnginesClosureTest
*/
/**
* Testing TLS engines closing using each of the supported cipher suites.
*/
public class TLSEnginesClosureTest {
public static void main(String[] args) {
EnginesClosureTest.main(args);
}
}

View File

@ -21,14 +21,16 @@
* questions.
*/
//
// SunJSSE does not support dynamic system properties, no way to re-use
// system properties in samevm/agentvm mode.
//
/*
* @test
* @bug 4814140
* @summary AppInputStream: read can block a close
* @run main/othervm ReadBlocksClose
*
* SunJSSE does not support dynamic system properties, no way to re-use
* system properties in samevm/agentvm mode.
* @run main/othervm -Djdk.tls.acknowledgeCloseNotify=true ReadBlocksClose
* @author Brad Wetmore
*/
@ -141,7 +143,8 @@ public class ReadBlocksClose {
System.out.println("Closing Thread started");
Thread.sleep(3000);
System.out.println("Closing Thread closing");
sslIS.close();
sslOS.close();
System.out.println("Closing Thread closed");
} catch (Exception e) {
RuntimeException rte =
new RuntimeException("Check this out");

View File

@ -68,11 +68,6 @@ public class CloseStart {
}
}
private static void runTest1(SSLEngine ssle) throws Exception {
ssle.closeInbound();
checkDone(ssle);
}
private static void runTest2(SSLEngine ssle) throws Exception {
ssle.closeOutbound();
checkDone(ssle);
@ -81,10 +76,16 @@ public class CloseStart {
public static void main(String args[]) throws Exception {
SSLEngine ssle = createSSLEngine(keyFilename, trustFilename);
runTest1(ssle);
ssle.closeInbound();
if (!ssle.isInboundDone()) {
throw new Exception("isInboundDone isn't done");
}
ssle = createSSLEngine(keyFilename, trustFilename);
runTest2(ssle);
ssle.closeOutbound();
if (!ssle.isOutboundDone()) {
throw new Exception("isOutboundDone isn't done");
}
System.out.println("Test Passed.");
}

View File

@ -280,6 +280,7 @@ public class SSLEngineDeadlock {
log("\tClosing clientEngine's *OUTBOUND*...");
clientEngine.closeOutbound();
serverEngine.closeOutbound();
dataDone = true;
}
}

View File

@ -127,8 +127,7 @@ public class SSLEngineKeyLimit {
output.shouldNotContain("KeyUpdate: write key updated");
output.shouldNotContain("KeyUpdate: read key updated");
} else {
output.shouldContain("KeyUpdate: triggered, read side");
output.shouldContain("KeyUpdate: triggered, write side");
output.shouldContain("trigger key update");
output.shouldContain("KeyUpdate: write key updated");
output.shouldContain("KeyUpdate: read key updated");
}

View File

@ -106,11 +106,11 @@ public class TLS13BeginHandshake {
checkTransfer(clientOut, serverIn);
System.out.println("\tClosing...");
clientEngine.closeOutbound();
serverEngine.closeOutbound();
done++;
continue;
}
}
}
private static boolean isEngineClosed(SSLEngine engine) {

View File

@ -36,6 +36,7 @@
import javax.net.ssl.*;
import java.io.*;
import java.net.SocketException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -123,6 +124,9 @@ public class AsyncSSLSocketClose implements Runnable {
os.write(ba);
System.out.println(count + " bytes written");
}
} catch (SocketException se) {
// the closing may be in progress
System.out.println("interrupted? " + se);
} catch (Exception e) {
if (socket.isClosed() || socket.isOutputShutdown()) {
System.out.println("interrupted, the socket is closed");

View File

@ -119,7 +119,7 @@ public class SSLSocketKeyLimit {
System.setProperty("test.java.opts",
"-Dtest.src=" + System.getProperty("test.src") +
" -Dtest.jdk=" + System.getProperty("test.jdk") +
" -Djavax.net.debug=ssl,handshake " +
" -Djavax.net.debug=ssl,handshake" +
" -Djava.security.properties=" + f.getName());
System.out.println("test.java.opts: " +
@ -134,8 +134,7 @@ public class SSLSocketKeyLimit {
output.shouldNotContain("KeyUpdate: write key updated");
output.shouldNotContain("KeyUpdate: read key updated");
} else {
output.shouldContain("KeyUpdate: triggered, read side");
output.shouldContain("KeyUpdate: triggered, write side");
output.shouldContain("trigger key update");
output.shouldContain("KeyUpdate: write key updated");
output.shouldContain("KeyUpdate: read key updated");
}

View File

@ -0,0 +1,329 @@
/*
* Copyright (c) 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.
*
* 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.
*/
//
// SunJSSE does not support dynamic system properties, no way to re-use
// system properties in samevm/agentvm mode.
//
/*
* @test
* @bug 8208642
* @summary Server initiated TLSv1.2 renegotiation fails if Java
* client allows TLSv1.3
* @run main/othervm ServerRenegoWithTwoVersions TLSv1 SSLv3
* @run main/othervm ServerRenegoWithTwoVersions TLSv1 TLSv1.1
* @run main/othervm ServerRenegoWithTwoVersions TLSv1.2 TLSv1.1
* @run main/othervm ServerRenegoWithTwoVersions TLSv1.3 TLSv1.2
*/
import java.io.*;
import java.net.*;
import java.security.Security;
import javax.net.ssl.*;
public class ServerRenegoWithTwoVersions implements
HandshakeCompletedListener {
static byte handshakesCompleted = 0;
/*
* Define what happens when handshaking is completed
*/
public void handshakeCompleted(HandshakeCompletedEvent event) {
synchronized (this) {
handshakesCompleted++;
System.out.println("Session: " + event.getSession().toString());
System.out.println("Seen handshake completed #" +
handshakesCompleted);
}
}
/*
* =============================================================
* Set the various variables needed for the tests, then
* specify what tests to run on each side.
*/
/*
* Should we run the client or server in a separate thread?
* Both sides can throw exceptions, but do you have a preference
* as to which side should be the main thread.
*/
static boolean separateServerThread = false;
/*
* Where do we find the keystores?
*/
static String pathToStores = "../../../../javax/net/ssl/etc";
static String keyStoreFile = "keystore";
static String trustStoreFile = "truststore";
static String passwd = "passphrase";
/*
* Is the server ready to serve?
*/
volatile static boolean serverReady = false;
/*
* Turn on SSL debugging?
*/
static boolean debug = false;
/*
* If the client or server is doing some kind of object creation
* that the other side depends on, and that thread prematurely
* exits, you may experience a hang. The test harness will
* terminate all hung threads after its timeout has expired,
* currently 3 minutes by default, but you might try to be
* smart about it....
*/
/*
* Define the server side of the test.
*
* If the server prematurely exits, serverReady will be set to true
* to avoid infinite hangs.
*/
void doServerSide() throws Exception {
SSLServerSocketFactory sslssf =
(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
SSLServerSocket sslServerSocket =
(SSLServerSocket) sslssf.createServerSocket(serverPort);
serverPort = sslServerSocket.getLocalPort();
/*
* Signal Client, we're ready for his connect.
*/
serverReady = true;
SSLSocket sslSocket = (SSLSocket)sslServerSocket.accept();
sslSocket.setEnabledProtocols(new String[] { serverProtocol });
sslSocket.addHandshakeCompletedListener(this);
InputStream sslIS = sslSocket.getInputStream();
OutputStream sslOS = sslSocket.getOutputStream();
for (int i = 0; i < 10; i++) {
sslIS.read();
sslOS.write(85);
sslOS.flush();
}
System.out.println("invalidating");
sslSocket.getSession().invalidate();
System.out.println("starting new handshake");
sslSocket.startHandshake();
for (int i = 0; i < 10; i++) {
System.out.println("sending/receiving data, iteration: " + i);
sslIS.read();
sslOS.write(85);
sslOS.flush();
}
sslSocket.close();
}
/*
* Define the client side of the test.
*
* If the server prematurely exits, serverReady will be set to true
* to avoid infinite hangs.
*/
void doClientSide() throws Exception {
/*
* Wait for server to get started.
*/
while (!serverReady) {
Thread.sleep(50);
}
SSLSocketFactory sslsf =
(SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket sslSocket = (SSLSocket)
sslsf.createSocket("localhost", serverPort);
sslSocket.setEnabledProtocols(
new String[] { serverProtocol, clientProtocol });
InputStream sslIS = sslSocket.getInputStream();
OutputStream sslOS = sslSocket.getOutputStream();
for (int i = 0; i < 10; i++) {
sslOS.write(280);
sslOS.flush();
sslIS.read();
}
for (int i = 0; i < 10; i++) {
sslOS.write(280);
sslOS.flush();
sslIS.read();
}
sslSocket.close();
}
/*
* =============================================================
* The remainder is just support stuff
*/
// use any free port by default
volatile int serverPort = 0;
volatile Exception serverException = null;
volatile Exception clientException = null;
// the specified protocol
private static String clientProtocol;
private static String serverProtocol;
public static void main(String[] args) throws Exception {
String keyFilename =
System.getProperty("test.src", "./") + "/" + pathToStores +
"/" + keyStoreFile;
String trustFilename =
System.getProperty("test.src", "./") + "/" + pathToStores +
"/" + trustStoreFile;
System.setProperty("javax.net.ssl.keyStore", keyFilename);
System.setProperty("javax.net.ssl.keyStorePassword", passwd);
System.setProperty("javax.net.ssl.trustStore", trustFilename);
System.setProperty("javax.net.ssl.trustStorePassword", passwd);
if (debug) {
System.setProperty("javax.net.debug", "all");
}
Security.setProperty("jdk.tls.disabledAlgorithms", "");
clientProtocol = args[0];
serverProtocol = args[1];
/*
* Start the tests.
*/
new ServerRenegoWithTwoVersions();
}
Thread clientThread = null;
Thread serverThread = null;
/*
* Primary constructor, used to drive remainder of the test.
*
* Fork off the other side, then do your work.
*/
ServerRenegoWithTwoVersions() throws Exception {
if (separateServerThread) {
startServer(true);
startClient(false);
} else {
startClient(true);
startServer(false);
}
/*
* Wait for other side to close down.
*/
if (separateServerThread) {
serverThread.join();
} else {
clientThread.join();
}
/*
* When we get here, the test is pretty much over.
*
* If the main thread excepted, that propagates back
* immediately. If the other thread threw an exception, we
* should report back.
*/
if (serverException != null) {
System.out.print("Server Exception:");
throw serverException;
}
if (clientException != null) {
System.out.print("Client Exception:");
throw clientException;
}
/*
* Give the Handshaker Thread a chance to run
*/
Thread.sleep(1000);
synchronized (this) {
if (handshakesCompleted != 2) {
throw new Exception("Didn't see 2 handshake completed events.");
}
}
}
void startServer(boolean newThread) throws Exception {
if (newThread) {
serverThread = new Thread() {
public void run() {
try {
doServerSide();
} catch (Exception e) {
/*
* Our server thread just died.
*
* Release the client, if not active already...
*/
System.err.println("Server died...");
serverReady = true;
serverException = e;
}
}
};
serverThread.start();
} else {
doServerSide();
}
}
void startClient(boolean newThread) throws Exception {
if (newThread) {
clientThread = new Thread() {
public void run() {
try {
doClientSide();
} catch (Exception e) {
/*
* Our client thread just died.
*/
System.err.println("Client died...");
clientException = e;
}
}
};
clientThread.start();
} else {
doClientSide();
}
}
}