/*
* Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketException;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLHandshakeException;
OutputRecord
implementation for SSLSocket
. /**
* {@code OutputRecord} implementation for {@code SSLSocket}.
*/
final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
private OutputStream deliverStream = null;
SSLSocketOutputRecord(HandshakeHash handshakeHash) {
this(handshakeHash, null);
}
SSLSocketOutputRecord(HandshakeHash handshakeHash,
TransportContext tc) {
super(handshakeHash, SSLCipher.SSLWriteCipher.nullTlsWriteCipher());
this.tc = tc;
this.packetSize = SSLRecord.maxRecordSize;
this.protocolVersion = ProtocolVersion.NONE;
}
@Override
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;
write(level);
write(description);
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine("WRITE: " + protocolVersion +
" " + ContentType.ALERT.name +
"(" + Alert.nameOf(description) + ")" +
", length = " + (count - headerSize));
}
// Encrypt the fragment and wrap up a record.
encrypt(writeCipher, ContentType.ALERT.id, headerSize);
// deliver this message
deliverStream.write(buf, 0, count); // may throw IOException
deliverStream.flush(); // may throw IOException
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine("Raw write",
(new ByteArrayInputStream(buf, 0, count)));
}
// reset the internal buffer
count = 0;
}
@Override
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;
if ((helloVersion == ProtocolVersion.SSL20Hello) &&
(source[offset] == SSLHandshake.CLIENT_HELLO.id) &&
// 5: recode header size
(source[offset + 4 + 2 + 32] == 0)) {
// V3 session ID is empty
// 4: handshake header size
// 2: client_version in ClientHello
// 32: random in ClientHello
ByteBuffer v2ClientHello = encodeV2ClientHello(
source, (offset + 4), (length - 4));
byte[] record = v2ClientHello.array(); // array offset is zero
int limit = v2ClientHello.limit();
handshakeHash.deliver(record, 2, (limit - 2));
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine(
"WRITE: SSLv2 ClientHello message" +
", length = " + limit);
}
// deliver this message
//
// Version 2 ClientHello message should be plaintext.
//
// No max fragment length negotiation.
deliverStream.write(record, 0, limit);
deliverStream.flush();
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine("Raw write",
(new ByteArrayInputStream(record, 0, limit)));
}
return;
}
}
byte handshakeType = source[0];
if (handshakeHash.isHashable(handshakeType)) {
handshakeHash.deliver(source, offset, length);
}
int fragLimit = getFragLimit();
int position = headerSize + writeCipher.getExplicitNonceSize();
if (count == 0) {
count = position;
}
if ((count - position) < (fragLimit - length)) {
write(source, offset, length);
return;
}
for (int limit = (offset + length); offset < limit;) {
int remains = (limit - offset) + (count - position);
int fragLen = Math.min(fragLimit, remains);
// use the buf of ByteArrayOutputStream
write(source, offset, fragLen);
if (remains < fragLimit) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine(
"WRITE: " + protocolVersion +
" " + ContentType.HANDSHAKE.name +
", length = " + (count - headerSize));
}
// Encrypt the fragment and wrap up a record.
encrypt(writeCipher, ContentType.HANDSHAKE.id, headerSize);
// deliver this message
deliverStream.write(buf, 0, count); // may throw IOException
deliverStream.flush(); // may throw IOException
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine("Raw write",
(new ByteArrayInputStream(buf, 0, count)));
}
// reset the offset
offset += fragLen;
// reset the internal buffer
count = position;
}
}
@Override
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();
count = position;
write((byte)1); // byte 1: change_cipher_spec(
// Encrypt the fragment and wrap up a record.
encrypt(writeCipher, ContentType.CHANGE_CIPHER_SPEC.id, headerSize);
// deliver this message
deliverStream.write(buf, 0, count); // may throw IOException
// deliverStream.flush(); // flush in Finished
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine("Raw write",
(new ByteArrayInputStream(buf, 0, count)));
}
// reset the internal buffer
count = 0;
}
@Override
public synchronized void flush() throws IOException {
int position = headerSize + writeCipher.getExplicitNonceSize();
if (count <= position) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine(
"WRITE: " + protocolVersion +
" " + ContentType.HANDSHAKE.name +
", length = " + (count - headerSize));
}
// Encrypt the fragment and wrap up a record.
encrypt(writeCipher, ContentType.HANDSHAKE.id, headerSize);
// deliver this message
deliverStream.write(buf, 0, count); // may throw IOException
deliverStream.flush(); // may throw IOException
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine("Raw write",
(new ByteArrayInputStream(buf, 0, count)));
}
// reset the internal buffer
count = 0; // DON'T use position
}
@Override
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(
"sequence number extremely close to overflow " +
"(2^64-1 packets). Closing connection.");
}
throw new SSLHandshakeException("sequence number overflow");
}
boolean isFirstRecordOfThePayload = true;
for (int limit = (offset + length); offset < limit;) {
int fragLen;
if (packetSize > 0) {
fragLen = Math.min(maxRecordSize, packetSize);
fragLen =
writeCipher.calculateFragmentSize(fragLen, headerSize);
fragLen = Math.min(fragLen, Record.maxDataSize);
} else {
fragLen = Record.maxDataSize;
}
// Calculate more impact, for example TLS 1.3 padding.
fragLen = calculateFragmentSize(fragLen);
if (isFirstRecordOfThePayload && needToSplitPayload()) {
fragLen = 1;
isFirstRecordOfThePayload = false;
} else {
fragLen = Math.min(fragLen, (limit - offset));
}
// use the buf of ByteArrayOutputStream
int position = headerSize + writeCipher.getExplicitNonceSize();
count = position;
write(source, offset, fragLen);
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine(
"WRITE: " + protocolVersion +
" " + ContentType.APPLICATION_DATA.name +
", length = " + (count - position));
}
// Encrypt the fragment and wrap up a record.
encrypt(writeCipher, ContentType.APPLICATION_DATA.id, headerSize);
// deliver this message
deliverStream.write(buf, 0, count); // may throw IOException
deliverStream.flush(); // may throw IOException
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine("Raw write",
(new ByteArrayInputStream(buf, 0, count)));
}
// reset the internal buffer
count = 0;
if (isFirstAppOutputRecord) {
isFirstAppOutputRecord = false;
}
offset += fragLen;
}
}
@Override
synchronized void setDeliverStream(OutputStream outputStream) {
this.deliverStream = outputStream;
}
/*
* Need to split the payload except the following cases:
*
* 1. protocol version is TLS 1.1 or later;
* 2. bulk cipher does not use CBC mode, including null bulk cipher suites.
* 3. the payload is the first application record of a freshly
* negotiated TLS session.
* 4. the CBC protection is disabled;
*
* By default, we counter chosen plaintext issues on CBC mode
* ciphersuites in SSLv3/TLS1.0 by sending one byte of application
* data in the first record of every payload, and the rest in
* subsequent record(s). Note that the issues have been solved in
* TLS 1.1 or later.
*
* It is not necessary to split the very first application record of
* a freshly negotiated TLS session, as there is no previous
* application data to guess. To improve compatibility, we will not
* split such records.
*
* This avoids issues in the outbound direction. For a full fix,
* the peer must have similar protections.
*/
private boolean needToSplitPayload() {
return (!protocolVersion.useTLS11PlusSpec()) &&
writeCipher.isCBCMode() && !isFirstAppOutputRecord &&
Record.enableCBCProtection;
}
private int getFragLimit() {
int fragLimit;
if (packetSize > 0) {
fragLimit = Math.min(maxRecordSize, packetSize);
fragLimit =
writeCipher.calculateFragmentSize(fragLimit, headerSize);
fragLimit = Math.min(fragLimit, Record.maxDataSize);
} else {
fragLimit = Record.maxDataSize;
}
// Calculate more impact, for example TLS 1.3 padding.
fragLimit = calculateFragmentSize(fragLimit);
return fragLimit;
}
}