/*
 * 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.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import javax.crypto.BadPaddingException;
import sun.security.ssl.SSLCipher.SSLReadCipher;

InputRecord takes care of the management of SSL/TLS/DTLS input records, including buffering, decryption, handshake messages marshal, etc.
Author:David Brownell
/** * {@code InputRecord} takes care of the management of SSL/TLS/DTLS input * records, including buffering, decryption, handshake messages marshal, etc. * * @author David Brownell */
abstract class InputRecord implements Record, Closeable { SSLReadCipher readCipher; // Needed for KeyUpdate, used after Handshake.Finished TransportContext tc; final HandshakeHash handshakeHash; boolean isClosed; // The ClientHello version to accept. If set to ProtocolVersion.SSL20Hello // and the first message we read is a ClientHello in V2 format, we convert // it to V3. Otherwise we throw an exception when encountering a V2 hello. ProtocolVersion helloVersion; // fragment size int fragmentSize; InputRecord(HandshakeHash handshakeHash, SSLReadCipher readCipher) { this.readCipher = readCipher; this.helloVersion = ProtocolVersion.TLS10; this.handshakeHash = handshakeHash; this.isClosed = false; this.fragmentSize = Record.maxDataSize; } void setHelloVersion(ProtocolVersion helloVersion) { this.helloVersion = helloVersion; } boolean seqNumIsHuge() { return (readCipher.authenticator != null) && readCipher.authenticator.seqNumIsHuge(); } boolean isEmpty() { return false; } // apply to DTLS SSLEngine void expectingFinishFlight() { // blank } // apply to DTLS SSLEngine void finishHandshake() { // blank }
Prevent any more data from being read into this record, and flag the record as holding no data.
/** * Prevent any more data from being read into this record, * and flag the record as holding no data. */
@Override public synchronized void close() throws IOException { if (!isClosed) { isClosed = true; readCipher.dispose(); } } synchronized boolean isClosed() { return isClosed; } // apply to SSLSocket and SSLEngine void changeReadCiphers(SSLReadCipher readCipher) { /* * 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. */ readCipher.dispose(); this.readCipher = readCipher; } // change fragment size void changeFragmentSize(int fragmentSize) { this.fragmentSize = fragmentSize; } /* * Check if there is enough inbound data in the ByteBuffer to make * a inbound packet. * * @return -1 if there are not enough bytes to tell (small header), */ // apply to SSLEngine only int bytesInCompletePacket( ByteBuffer[] srcs, int srcsOffset, int srcsLength) throws IOException { throw new UnsupportedOperationException("Not supported yet."); } // apply to SSLSocket only int bytesInCompletePacket() throws IOException { throw new UnsupportedOperationException(); } // apply to SSLSocket only void setReceiverStream(InputStream inputStream) { throw new UnsupportedOperationException(); } // apply to DTLS SSLEngine only Plaintext acquirePlaintext() throws IOException, BadPaddingException { throw new UnsupportedOperationException(); } // read, decrypt and decompress the network record. // abstract Plaintext[] decode(ByteBuffer[] srcs, int srcsOffset, int srcsLength) throws IOException, BadPaddingException; // apply to SSLSocket only void setDeliverStream(OutputStream outputStream) { throw new UnsupportedOperationException(); } // calculate plaintext fragment size // // apply to SSLEngine only int estimateFragmentSize(int packetSize) { throw new UnsupportedOperationException(); } // // shared helpers // // Not apply to DTLS static ByteBuffer convertToClientHello(ByteBuffer packet) { int srcPos = packet.position(); byte firstByte = packet.get(); byte secondByte = packet.get(); int recordLen = (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2; packet.position(srcPos + 3); // the V2ClientHello record header byte majorVersion = packet.get(); byte minorVersion = packet.get(); int cipherSpecLen = ((packet.get() & 0xFF) << 8) + (packet.get() & 0xFF); int sessionIdLen = ((packet.get() & 0xFF) << 8) + (packet.get() & 0xFF); int nonceLen = ((packet.get() & 0xFF) << 8) + (packet.get() & 0xFF); // Required space for the target SSLv3 ClientHello message. // 5: record header size // 4: handshake header size // 2: ClientHello.client_version // 32: ClientHello.random // 1: length byte of ClientHello.session_id // 2: length bytes of ClientHello.cipher_suites // 2: empty ClientHello.compression_methods int requiredSize = 48 + sessionIdLen + ((cipherSpecLen * 2 ) / 3); byte[] converted = new byte[requiredSize]; /* * Build the first part of the V3 record header from the V2 one * that's now buffered up. (Lengths are fixed up later). */ // Note: need not to set the header actually. converted[0] = ContentType.HANDSHAKE.id; converted[1] = majorVersion; converted[2] = minorVersion; // header [3..4] for handshake message length // required size is 5; /* * Store the generic V3 handshake header: 4 bytes */ converted[5] = 1; // HandshakeMessage.ht_client_hello // buf [6..8] for length of ClientHello (int24) // required size += 4; /* * ClientHello header starts with SSL version */ converted[9] = majorVersion; converted[10] = minorVersion; // required size += 2; int pointer = 11; /* * Copy Random value/nonce ... if less than the 32 bytes of * a V3 "Random", right justify and zero pad to the left. Else * just take the last 32 bytes. */ int offset = srcPos + 11 + cipherSpecLen + sessionIdLen; if (nonceLen < 32) { for (int i = 0; i < (32 - nonceLen); i++) { converted[pointer++] = 0; } packet.position(offset); packet.get(converted, pointer, nonceLen); pointer += nonceLen; } else { packet.position(offset + nonceLen - 32); packet.get(converted, pointer, 32); pointer += 32; } /* * Copy session ID (only one byte length!) */ offset -= sessionIdLen; converted[pointer++] = (byte)(sessionIdLen & 0xFF); packet.position(offset); packet.get(converted, pointer, sessionIdLen); /* * Copy and translate cipher suites ... V2 specs with first byte zero * are really V3 specs (in the last 2 bytes), just copy those and drop * the other ones. Preference order remains unchanged. * * Example: Netscape Navigator 3.0 (exportable) says: * * 0/3, SSL_RSA_EXPORT_WITH_RC4_40_MD5 * 0/6, SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5 * * Microsoft Internet Explorer 3.0 (exportable) supports only * * 0/3, SSL_RSA_EXPORT_WITH_RC4_40_MD5 */ int j; offset -= cipherSpecLen; packet.position(offset); j = pointer + 2; for (int i = 0; i < cipherSpecLen; i += 3) { if (packet.get() != 0) { // Ignore version 2.0 specific cipher suite. Clients // should also include the version 3.0 equivalent in // the V2ClientHello message. packet.get(); // ignore the 2nd byte packet.get(); // ignore the 3rd byte continue; } converted[j++] = packet.get(); converted[j++] = packet.get(); } j -= pointer + 2; converted[pointer++] = (byte)((j >>> 8) & 0xFF); converted[pointer++] = (byte)(j & 0xFF); pointer += j; /* * Append compression methods (default/null only) */ converted[pointer++] = 1; converted[pointer++] = 0; // Session.compression_null /* * Fill in lengths of the messages we synthesized (nested: * V3 handshake message within V3 record). */ // Note: need not to set the header actually. int fragLen = pointer - 5; // TLSPlaintext.length converted[3] = (byte)((fragLen >>> 8) & 0xFF); converted[4] = (byte)(fragLen & 0xFF); /* * Handshake.length, length of ClientHello message */ fragLen = pointer - 9; // Handshake.length converted[6] = (byte)((fragLen >>> 16) & 0xFF); converted[7] = (byte)((fragLen >>> 8) & 0xFF); converted[8] = (byte)(fragLen & 0xFF); // consume the full record packet.position(srcPos + recordLen); // Need no header bytes. return ByteBuffer.wrap(converted, 5, pointer - 5); // 5: header size } // Extract an SSL/(D)TLS record from the specified source buffers. static ByteBuffer extract( ByteBuffer[] buffers, int offset, int length, int headerSize) { boolean hasFullHeader = false; int contentLen = -1; for (int i = offset, j = 0; i < (offset + length) && j < headerSize; i++) { int remains = buffers[i].remaining(); int pos = buffers[i].position(); for (int k = 0; k < remains && j < headerSize; j++, k++) { byte b = buffers[i].get(pos + k); if (j == (headerSize - 2)) { contentLen = ((b & 0xFF) << 8); } else if (j == (headerSize -1)) { contentLen |= (b & 0xFF); hasFullHeader = true; break; } } } if (!hasFullHeader) { throw new BufferUnderflowException(); } int packetLen = headerSize + contentLen; int remains = 0; for (int i = offset; i < offset + length; i++) { remains += buffers[i].remaining(); if (remains >= packetLen) { break; } } if (remains < packetLen) { throw new BufferUnderflowException(); } byte[] packet = new byte[packetLen]; int packetOffset = 0; int packetSpaces = packetLen; for (int i = offset; i < offset + length; i++) { if (buffers[i].hasRemaining()) { int len = Math.min(packetSpaces, buffers[i].remaining()); buffers[i].get(packet, packetOffset, len); packetOffset += len; packetSpaces -= len; } if (packetSpaces <= 0) { break; } } return ByteBuffer.wrap(packet); } }