| /* |
| * 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; |
| |
| /** |
| * {@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; |
| } |
| } |