mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
8207009: TLS 1.3 half-close and synchronization issues
Reviewed-by: jnimeh, mullan, wetmore
This commit is contained in:
parent
a83af4505e
commit
66e8f27bd8
36 changed files with 1467 additions and 621 deletions
|
@ -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() {
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue