/*
 * Copyright (c) 1996, 2021, 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.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.locks.ReentrantLock;
import sun.security.ssl.SSLCipher.SSLWriteCipher;

OutputRecord takes care of the management of SSL/(D)TLS output records, including buffering, encryption, handshake messages marshal, etc.
Author:David Brownell
/** * {@code OutputRecord} takes care of the management of SSL/(D)TLS * output records, including buffering, encryption, handshake * messages marshal, etc. * * @author David Brownell */
abstract class OutputRecord extends ByteArrayOutputStream implements Record, Closeable { SSLWriteCipher writeCipher; // Needed for KeyUpdate, used after Handshake.Finished TransportContext tc; final HandshakeHash handshakeHash; boolean firstMessage; // current protocol version, sent as record version ProtocolVersion protocolVersion; // version for the ClientHello message. Only relevant if this is a // client handshake record. If set to ProtocolVersion.SSL20Hello, // the V3 client hello is converted to V2 format. ProtocolVersion helloVersion; // Is it the first application record to write? boolean isFirstAppOutputRecord = true; // packet size int packetSize; // fragment size private int fragmentSize; // closed or not? volatile boolean isClosed; final ReentrantLock recordLock = new ReentrantLock(); /* * Mappings from V3 cipher suite encodings to their pure V2 equivalents. * This is taken from the SSL V3 specification, Appendix E. */ private static final int[] V3toV2CipherMap1 = {-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; this.firstMessage = true; this.fragmentSize = Record.maxDataSize; this.handshakeHash = handshakeHash; // Please set packetSize and protocolVersion in the implementation. } void setVersion(ProtocolVersion protocolVersion) { recordLock.lock(); try { this.protocolVersion = protocolVersion; } finally { recordLock.unlock(); } } /* * Updates helloVersion of this record. */ void setHelloVersion(ProtocolVersion helloVersion) { recordLock.lock(); try { this.helloVersion = helloVersion; } finally { recordLock.unlock(); } } /* * Return true iff the record is empty -- to avoid doing the work * of sending empty records over the network. */ boolean isEmpty() { return false; } boolean seqNumIsHuge() { recordLock.lock(); try { return (writeCipher.authenticator != null) && writeCipher.authenticator.seqNumIsHuge(); } finally { recordLock.unlock(); } } // SSLEngine and SSLSocket abstract void encodeAlert(byte level, byte description) throws IOException; // SSLEngine and SSLSocket abstract void encodeHandshake(byte[] buffer, int offset, int length) throws IOException; // SSLEngine and SSLSocket abstract void encodeChangeCipherSpec() throws IOException; // apply to SSLEngine only Ciphertext encode( ByteBuffer[] srcs, int srcsOffset, int srcsLength, ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { throw new UnsupportedOperationException(); } // apply to SSLEngine only void encodeV2NoCipher() throws IOException { throw new UnsupportedOperationException(); } // apply to SSLSocket only void deliver( byte[] source, int offset, int length) throws IOException { throw new UnsupportedOperationException(); } // apply to SSLSocket only void setDeliverStream(OutputStream outputStream) { throw new UnsupportedOperationException(); } // Change write ciphers, may use change_cipher_spec record. void changeWriteCiphers(SSLWriteCipher writeCipher, boolean useChangeCipherSpec) throws IOException { recordLock.lock(); try { if (isClosed()) { if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } return; } if (useChangeCipherSpec) { encodeChangeCipherSpec(); } /* * Dispose of any intermediate state in the underlying cipher. * For PKCS11 ciphers, this will release any attached sessions, * and thus make finalization faster. * * Since MAC's doFinal() is called for every SSL/TLS packet, it's * not necessary to do the same with MAC's. */ writeCipher.dispose(); this.writeCipher = writeCipher; this.isFirstAppOutputRecord = true; } finally { recordLock.unlock(); } } // Change write ciphers using key_update handshake message. void changeWriteCiphers(SSLWriteCipher writeCipher, byte keyUpdateRequest) throws IOException { recordLock.lock(); try { 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; } finally { recordLock.unlock(); } } void changePacketSize(int packetSize) { recordLock.lock(); try { this.packetSize = packetSize; } finally { recordLock.unlock(); } } void changeFragmentSize(int fragmentSize) { recordLock.lock(); try { this.fragmentSize = fragmentSize; } finally { recordLock.unlock(); } } int getMaxPacketSize() { recordLock.lock(); try { return packetSize; } finally { recordLock.unlock(); } } // apply to DTLS SSLEngine void initHandshaker() { // blank } // apply to DTLS SSLEngine void finishHandshake() { // blank } // apply to DTLS SSLEngine void launchRetransmission() { // blank } @Override public void close() throws IOException { recordLock.lock(); try { if (isClosed) { return; } isClosed = true; writeCipher.dispose(); } finally { recordLock.unlock(); } } boolean isClosed() { return isClosed; } // // shared helpers // private static final class T13PaddingHolder { private static final byte[] zeros = new byte[16]; } int calculateFragmentSize(int fragmentLimit) { if (fragmentSize > 0) { fragmentLimit = Math.min(fragmentLimit, fragmentSize); } if (protocolVersion.useTLS13PlusSpec()) { // No negative integer checking as the fragment capacity should // have been ensured. return fragmentLimit - T13PaddingHolder.zeros.length - 1; } return fragmentLimit; } // Encrypt a fragment and wrap up a record. // // To be consistent with the spec of SSLEngine.wrap() methods, the // destination ByteBuffer's position is updated to reflect the amount // of data produced. The limit remains the same. static long encrypt( SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, int headerOffset, int dstLim, int headerSize, ProtocolVersion protocolVersion) { boolean isDTLS = protocolVersion.isDTLS; if (isDTLS) { if (protocolVersion.useTLS13PlusSpec()) { return d13Encrypt(encCipher, contentType, destination, headerOffset, dstLim, headerSize, protocolVersion); } else { return d10Encrypt(encCipher, contentType, destination, headerOffset, dstLim, headerSize, protocolVersion); } } else { if (protocolVersion.useTLS13PlusSpec()) { return t13Encrypt(encCipher, contentType, destination, headerOffset, dstLim, headerSize, protocolVersion); } else { return t10Encrypt(encCipher, contentType, destination, headerOffset, dstLim, headerSize, protocolVersion); } } } private static long d13Encrypt( SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, int headerOffset, int dstLim, int headerSize, ProtocolVersion protocolVersion) { throw new UnsupportedOperationException("Not supported yet."); } private static long d10Encrypt( SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, int headerOffset, int dstLim, int headerSize, ProtocolVersion protocolVersion) { byte[] sequenceNumber = encCipher.authenticator.sequenceNumber(); encCipher.encrypt(contentType, destination); // Finish out the record header. int fragLen = destination.limit() - headerOffset - headerSize; destination.put(headerOffset, contentType); // content type destination.put(headerOffset + 1, protocolVersion.major); destination.put(headerOffset + 2, protocolVersion.minor); // epoch and sequence_number destination.put(headerOffset + 3, sequenceNumber[0]); destination.put(headerOffset + 4, sequenceNumber[1]); destination.put(headerOffset + 5, sequenceNumber[2]); destination.put(headerOffset + 6, sequenceNumber[3]); destination.put(headerOffset + 7, sequenceNumber[4]); destination.put(headerOffset + 8, sequenceNumber[5]); destination.put(headerOffset + 9, sequenceNumber[6]); destination.put(headerOffset + 10, sequenceNumber[7]); // fragment length destination.put(headerOffset + 11, (byte)(fragLen >> 8)); destination.put(headerOffset + 12, (byte)fragLen); // Update destination position to reflect the amount of data produced. destination.position(destination.limit()); return Authenticator.toLong(sequenceNumber); } private static long t13Encrypt( SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, int headerOffset, int dstLim, int headerSize, ProtocolVersion protocolVersion) { if (!encCipher.isNullCipher()) { // inner plaintext, using zero length padding. int endOfPt = destination.limit(); int startOfPt = destination.position(); destination.position(endOfPt); destination.limit(endOfPt + 1 + T13PaddingHolder.zeros.length); destination.put(contentType); destination.put(T13PaddingHolder.zeros); destination.position(startOfPt); } // use the right TLSCiphertext.opaque_type and legacy_record_version ProtocolVersion pv = protocolVersion; if (!encCipher.isNullCipher()) { pv = ProtocolVersion.TLS12; contentType = ContentType.APPLICATION_DATA.id; } else if (protocolVersion.useTLS13PlusSpec()) { pv = ProtocolVersion.TLS12; } byte[] sequenceNumber = encCipher.authenticator.sequenceNumber(); encCipher.encrypt(contentType, destination); // Finish out the record header. int fragLen = destination.limit() - headerOffset - headerSize; destination.put(headerOffset, contentType); destination.put(headerOffset + 1, pv.major); destination.put(headerOffset + 2, pv.minor); // fragment length destination.put(headerOffset + 3, (byte)(fragLen >> 8)); destination.put(headerOffset + 4, (byte)fragLen); // Update destination position to reflect the amount of data produced. destination.position(destination.limit()); return Authenticator.toLong(sequenceNumber); } private static long t10Encrypt( SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, int headerOffset, int dstLim, int headerSize, ProtocolVersion protocolVersion) { byte[] sequenceNumber = encCipher.authenticator.sequenceNumber(); encCipher.encrypt(contentType, destination); // Finish out the record header. int fragLen = destination.limit() - headerOffset - headerSize; destination.put(headerOffset, contentType); // content type destination.put(headerOffset + 1, protocolVersion.major); destination.put(headerOffset + 2, protocolVersion.minor); // fragment length destination.put(headerOffset + 3, (byte)(fragLen >> 8)); destination.put(headerOffset + 4, (byte)fragLen); // Update destination position to reflect the amount of data produced. destination.position(destination.limit()); return Authenticator.toLong(sequenceNumber); } // Encrypt a fragment and wrap up a record. // // Uses the internal expandable buf variable and the current // protocolVersion variable. long encrypt( SSLWriteCipher encCipher, byte contentType, int headerSize) { if (protocolVersion.useTLS13PlusSpec()) { return t13Encrypt(encCipher, contentType, headerSize); } else { return t10Encrypt(encCipher, contentType, headerSize); } } private long t13Encrypt( SSLWriteCipher encCipher, byte contentType, int headerSize) { if (!encCipher.isNullCipher()) { // inner plaintext write(contentType); write(T13PaddingHolder.zeros, 0, T13PaddingHolder.zeros.length); } byte[] sequenceNumber = encCipher.authenticator.sequenceNumber(); int contentLen = count - headerSize; // ensure the capacity int requiredPacketSize = encCipher.calculatePacketSize(contentLen, headerSize); if (requiredPacketSize > buf.length) { byte[] newBuf = new byte[requiredPacketSize]; System.arraycopy(buf, 0, newBuf, 0, count); buf = newBuf; } // use the right TLSCiphertext.opaque_type and legacy_record_version ProtocolVersion pv; if (!encCipher.isNullCipher()) { pv = ProtocolVersion.TLS12; contentType = ContentType.APPLICATION_DATA.id; } else { pv = ProtocolVersion.TLS12; } ByteBuffer destination = ByteBuffer.wrap(buf, headerSize, contentLen); count = headerSize + encCipher.encrypt(contentType, destination); // Fill out the header, write it and the message. int fragLen = count - headerSize; buf[0] = contentType; buf[1] = pv.major; buf[2] = pv.minor; buf[3] = (byte)((fragLen >> 8) & 0xFF); buf[4] = (byte)(fragLen & 0xFF); return Authenticator.toLong(sequenceNumber); } 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 requiredPacketSize = encCipher.calculatePacketSize(contentLen, headerSize); if (requiredPacketSize > buf.length) { byte[] newBuf = new byte[requiredPacketSize]; System.arraycopy(buf, 0, newBuf, 0, count); buf = newBuf; } ByteBuffer destination = ByteBuffer.wrap(buf, position, contentLen); count = headerSize + encCipher.encrypt(contentType, destination); // Fill out the header, write it and the message. int fragLen = count - headerSize; buf[0] = contentType; buf[1] = protocolVersion.major; buf[2] = protocolVersion.minor; buf[3] = (byte)((fragLen >> 8) & 0xFF); buf[4] = (byte)(fragLen & 0xFF); return Authenticator.toLong(sequenceNumber); } static ByteBuffer encodeV2ClientHello( byte[] fragment, int offset, int length) throws IOException { int v3SessIdLenOffset = offset + 34; // 2: client_version // 32: random int v3SessIdLen = fragment[v3SessIdLenOffset]; int v3CSLenOffset = v3SessIdLenOffset + 1 + v3SessIdLen; int v3CSLen = ((fragment[v3CSLenOffset] & 0xff) << 8) + (fragment[v3CSLenOffset + 1] & 0xff); int cipherSpecs = v3CSLen / 2; // 2: cipher spec size // Estimate the max V2ClientHello message length // // 11: header size // (cipherSpecs * 6): cipher_specs // 6: one cipher suite may need 6 bytes, see V3toV2CipherSuite. // 3: placeholder for the TLS_EMPTY_RENEGOTIATION_INFO_SCSV // signaling cipher suite // 32: challenge size int v2MaxMsgLen = 11 + (cipherSpecs * 6) + 3 + 32; // Create a ByteBuffer backed by an accessible byte array. byte[] dstBytes = new byte[v2MaxMsgLen]; ByteBuffer dstBuf = ByteBuffer.wrap(dstBytes); /* * Copy over the cipher specs. We don't care about actually * translating them for use with an actual V2 server since * we only talk V3. Therefore, just copy over the V3 cipher * spec values with a leading 0. */ int v3CSOffset = v3CSLenOffset + 2; // skip length field int v2CSLen = 0; dstBuf.position(11); boolean containsRenegoInfoSCSV = false; for (int i = 0; i < cipherSpecs; i++) { byte byte1, byte2; byte1 = fragment[v3CSOffset++]; byte2 = fragment[v3CSOffset++]; v2CSLen += V3toV2CipherSuite(dstBuf, byte1, byte2); if (!containsRenegoInfoSCSV && byte1 == (byte)0x00 && byte2 == (byte)0xFF) { containsRenegoInfoSCSV = true; } } if (!containsRenegoInfoSCSV) { v2CSLen += V3toV2CipherSuite(dstBuf, (byte)0x00, (byte)0xFF); } /* * Copy in the nonce. */ dstBuf.put(fragment, (offset + 2), 32); /* * Build the first part of the V3 record header from the V2 one * that's now buffered up. (Lengths are fixed up later). */ int msgLen = dstBuf.position() - 2; // Exclude the legth field itself dstBuf.position(0); dstBuf.put((byte)(0x80 | ((msgLen >>> 8) & 0xFF))); // pos: 0 dstBuf.put((byte)(msgLen & 0xFF)); // pos: 1 dstBuf.put(SSLHandshake.CLIENT_HELLO.id); // pos: 2 dstBuf.put(fragment[offset]); // major version, pos: 3 dstBuf.put(fragment[offset + 1]); // minor version, pos: 4 dstBuf.put((byte)(v2CSLen >>> 8)); // pos: 5 dstBuf.put((byte)(v2CSLen & 0xFF)); // pos: 6 dstBuf.put((byte)0x00); // session_id_length, pos: 7 dstBuf.put((byte)0x00); // pos: 8 dstBuf.put((byte)0x00); // challenge_length, pos: 9 dstBuf.put((byte)32); // pos: 10 dstBuf.position(0); dstBuf.limit(msgLen + 2); return dstBuf; } private static int V3toV2CipherSuite(ByteBuffer dstBuf, byte byte1, byte byte2) { dstBuf.put((byte)0); dstBuf.put(byte1); dstBuf.put(byte2); if (((byte2 & 0xff) > 0xA) || (V3toV2CipherMap1[byte2] == -1)) { return 3; } dstBuf.put((byte)V3toV2CipherMap1[byte2]); dstBuf.put((byte)0); dstBuf.put((byte)V3toV2CipherMap3[byte2]); return 6; } }