/*
 * Copyright (c) 1996, 2013, 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.*;
import java.nio.*;
import java.util.Arrays;

import javax.net.ssl.SSLException;
import sun.misc.HexDumpEncoder;


SSL 3.0 records, as written to a TCP stream. Each record has a message area that starts out with data supplied by the application. It may grow/shrink due to compression and will be modified in place for mac-ing and encryption. Handshake records have additional needs, notably accumulation of a set of hashes which are used to establish that handshaking was done right. Handshake records usually have several handshake messages each, and we need message-level control over what's hashed.
Author:David Brownell
/** * SSL 3.0 records, as written to a TCP stream. * * Each record has a message area that starts out with data supplied by the * application. It may grow/shrink due to compression and will be modified * in place for mac-ing and encryption. * * Handshake records have additional needs, notably accumulation of a set * of hashes which are used to establish that handshaking was done right. * Handshake records usually have several handshake messages each, and we * need message-level control over what's hashed. * * @author David Brownell */
class OutputRecord extends ByteArrayOutputStream implements Record { private HandshakeHash handshakeHash; private int lastHashed; private boolean firstMessage; final private byte contentType; // 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. private ProtocolVersion helloVersion; /* Class and subclass dynamic debugging support */ static final Debug debug = Debug.getInstance("ssl"); /* * Default constructor makes a record supporting the maximum * SSL record size. It allocates the header bytes directly. * * @param type the content type for the record */ OutputRecord(byte type, int size) { super(size); this.protocolVersion = ProtocolVersion.DEFAULT; this.helloVersion = ProtocolVersion.DEFAULT_HELLO; firstMessage = true; count = headerSize; contentType = type; lastHashed = count; } OutputRecord(byte type) { this(type, recordSize(type)); }
Get the size of the buffer we need for records of the specified type.
/** * Get the size of the buffer we need for records of the specified * type. */
private static int recordSize(byte type) { if ((type == ct_change_cipher_spec) || (type == ct_alert)) { return maxAlertRecordSize; } else { return maxRecordSize; } } /* * Updates the SSL version of this record. */ synchronized void setVersion(ProtocolVersion protocolVersion) { this.protocolVersion = protocolVersion; } /* * Updates helloVersion of this record. */ synchronized void setHelloVersion(ProtocolVersion helloVersion) { this.helloVersion = helloVersion; } /* * Reset the record so that it can be refilled, starting * immediately after the header. */ public synchronized void reset() { super.reset(); count = headerSize; lastHashed = count; } /* * For handshaking, we need to be able to hash every byte above the * record marking layer. This is where we're guaranteed to see those * bytes, so this is where we can hash them. */ void setHandshakeHash(HandshakeHash handshakeHash) { assert(contentType == ct_handshake); this.handshakeHash = handshakeHash; } /* * We hash (the plaintext) on demand. There is one place where * we want to access the hash in the middle of a record: client * cert message gets hashed, and part of the same record is the * client cert verify message which uses that hash. So we track * how much of each record we've hashed so far. */ void doHashes() { int len = count - lastHashed; if (len > 0) { hashInternal(buf, lastHashed, len); lastHashed = count; } } /* * Need a helper function so we can hash the V2 hello correctly */ private void hashInternal(byte buf [], int offset, int len) { if (debug != null && Debug.isOn("data")) { try { HexDumpEncoder hd = new HexDumpEncoder(); System.out.println("[write] MD5 and SHA1 hashes: len = " + len); hd.encodeBuffer(new ByteArrayInputStream(buf, lastHashed, len), System.out); } catch (IOException e) { } } handshakeHash.update(buf, lastHashed, len); lastHashed = count; } /* * Return true iff the record is empty -- to avoid doing the work * of sending empty records over the network. */ boolean isEmpty() { return count == headerSize; } /* * Return true if the record is of a given alert. */ boolean isAlert(byte description) { // An alert is defined with a two bytes struct, // {byte level, byte description}, following after the header bytes. if (count > (headerSize + 1) && contentType == ct_alert) { return buf[headerSize + 1] == description; } return false; } /* * Compute the MAC and append it to this record. In case we * are automatically flushing a handshake stream, make sure we * have hashed the message first. */ void addMAC(MAC signer) throws IOException { // // when we support compression, hashing can't go here // since it'll need to be done on the uncompressed data, // and the MAC applies to the compressed data. // if (contentType == ct_handshake) { doHashes(); } if (signer.MAClen() != 0) { byte[] hash = signer.compute(contentType, buf, headerSize, count - headerSize, false); write(hash); } } /* * Encrypt ... length may grow due to block cipher padding */ void encrypt(CipherBox box) { int len = count - headerSize; count = headerSize + box.encrypt(buf, headerSize, len); } /* * Tell how full the buffer is ... for filling it with application or * handshake data. */ final int availableDataBytes() { int dataSize = count - headerSize; return maxDataSize - dataSize; } /* * Increases the capacity if necessary to ensure that it can hold * at least the number of elements specified by the minimum * capacity argument. * * Note that the increased capacity is only can be used for held * record buffer. Please DO NOT update the availableDataBytes() * according to the expended buffer capacity. * * @see availableDataBytes() */ private void ensureCapacity(int minCapacity) { // overflow-conscious code if (minCapacity > buf.length) { buf = Arrays.copyOf(buf, minCapacity); } } /* * Return the type of SSL record that's buffered here. */ final byte contentType() { return contentType; } /* * Write the record out on the stream. Note that you must have (in * order) compressed the data, appended the MAC, and encrypted it in * order for the record to be understood by the other end. (Some of * those steps will be null early in handshaking.) * * Note that this does no locking for the connection, it's required * that synchronization be done elsewhere. Also, this does its work * in a single low level write, for efficiency. */ void write(OutputStream s, boolean holdRecord, ByteArrayOutputStream heldRecordBuffer) throws IOException { /* * Don't emit content-free records. (Even change cipher spec * messages have a byte of data!) */ if (count == headerSize) { return; } int length = count - headerSize; // "should" really never write more than about 14 Kb... if (length < 0) { throw new SSLException("output record size too small: " + length); } if (debug != null && (Debug.isOn("record") || Debug.isOn("handshake"))) { if ((debug != null && Debug.isOn("record")) || contentType() == ct_change_cipher_spec) System.out.println(Thread.currentThread().getName() // v3.0/v3.1 ... + ", WRITE: " + protocolVersion + " " + InputRecord.contentName(contentType()) + ", length = " + length); } /* * If this is the initial ClientHello on this connection and * we're not trying to resume a (V3) session then send a V2 * ClientHello instead so we can detect V2 servers cleanly. */ if (firstMessage && useV2Hello()) { byte[] v3Msg = new byte[length - 4]; System.arraycopy(buf, headerSize + 4, v3Msg, 0, v3Msg.length); V3toV2ClientHello(v3Msg); handshakeHash.reset(); lastHashed = 2; doHashes(); if (debug != null && Debug.isOn("record")) { System.out.println( Thread.currentThread().getName() + ", WRITE: SSLv2 client hello message" + ", length = " + (count - 2)); // 2 byte SSLv2 header } } else { /* * Fill out the header, write it and the message. */ buf[0] = contentType; buf[1] = protocolVersion.major; buf[2] = protocolVersion.minor; buf[3] = (byte)(length >> 8); buf[4] = (byte)(length); } firstMessage = false; /* * The upper levels may want us to delay sending this packet so * multiple TLS Records can be sent in one (or more) TCP packets. * If so, add this packet to the heldRecordBuffer. * * NOTE: all writes have been synchronized by upper levels. */ int debugOffset = 0; if (holdRecord) { /* * If holdRecord is true, we must have a heldRecordBuffer. * * Don't worry about the override of writeBuffer(), because * when holdRecord is true, the implementation in this class * will be used. */ writeBuffer(heldRecordBuffer, buf, 0, count, debugOffset); } else { // It's time to send, do we have buffered data? // May or may not have a heldRecordBuffer. if (heldRecordBuffer != null && heldRecordBuffer.size() > 0) { int heldLen = heldRecordBuffer.size(); // Ensure the capacity of this buffer. ensureCapacity(count + heldLen); // Slide everything in the buffer to the right. System.arraycopy(buf, 0, buf, heldLen, count); // Prepend the held record to the buffer. System.arraycopy( heldRecordBuffer.toByteArray(), 0, buf, 0, heldLen); count += heldLen; // Clear the held buffer. heldRecordBuffer.reset(); // The held buffer has been dumped, set the debug dump offset. debugOffset = heldLen; } writeBuffer(s, buf, 0, count, debugOffset); } reset(); } /* * Actually do the write here. For SSLEngine's HS data, * we'll override this method and let it take the appropriate * action. */ void writeBuffer(OutputStream s, byte [] buf, int off, int len, int debugOffset) throws IOException { s.write(buf, off, len); s.flush(); // Output only the record from the specified debug offset. if (debug != null && Debug.isOn("packet")) { try { HexDumpEncoder hd = new HexDumpEncoder(); ByteBuffer bb = ByteBuffer.wrap( buf, off + debugOffset, len - debugOffset); System.out.println("[Raw write]: length = " + bb.remaining()); hd.encodeBuffer(bb, System.out); } catch (IOException e) { } } } /* * Return whether the buffer contains a ClientHello message that should * be converted to V2 format. */ private boolean useV2Hello() { return firstMessage && (helloVersion == ProtocolVersion.SSL20Hello) && (contentType == ct_handshake) && (buf[5] == HandshakeMessage.ht_client_hello) && (buf[headerSize + 4+2+32] == 0); // V3 session ID is empty } /* * Detect "old" servers which are capable of SSL V2.0 protocol ... for * example, Netscape Commerce 1.0 servers. The V3 message is in the * header and the bytes passed as parameter. This routine translates * the V3 message into an equivalent V2 one. * * Note that the translation will strip off all hello extensions as * SSL V2.0 does not support hello extension. */ private void V3toV2ClientHello(byte v3Msg []) throws SSLException { int v3SessionIdLenOffset = 2 + 32; // version + nonce int v3SessionIdLen = v3Msg[v3SessionIdLenOffset]; int v3CipherSpecLenOffset = v3SessionIdLenOffset + 1 + v3SessionIdLen; int v3CipherSpecLen = ((v3Msg[v3CipherSpecLenOffset] & 0xff) << 8) + (v3Msg[v3CipherSpecLenOffset + 1] & 0xff); int cipherSpecs = v3CipherSpecLen / 2; // 2 bytes each in V3 /* * 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 v3CipherSpecOffset = v3CipherSpecLenOffset + 2; // skip length int v2CipherSpecLen = 0; count = 11; boolean containsRenegoInfoSCSV = false; for (int i = 0; i < cipherSpecs; i++) { byte byte1, byte2; byte1 = v3Msg[v3CipherSpecOffset++]; byte2 = v3Msg[v3CipherSpecOffset++]; v2CipherSpecLen += V3toV2CipherSuite(byte1, byte2); if (!containsRenegoInfoSCSV && byte1 == (byte)0x00 && byte2 == (byte)0xFF) { containsRenegoInfoSCSV = true; } } if (!containsRenegoInfoSCSV) { v2CipherSpecLen += V3toV2CipherSuite((byte)0x00, (byte)0xFF); } /* * Build the first part of the V3 record header from the V2 one * that's now buffered up. (Lengths are fixed up later). */ buf[2] = HandshakeMessage.ht_client_hello; buf[3] = v3Msg[0]; // major version buf[4] = v3Msg[1]; // minor version buf[5] = (byte)(v2CipherSpecLen >>> 8); buf[6] = (byte)v2CipherSpecLen; buf[7] = 0; buf[8] = 0; // always no session buf[9] = 0; buf[10] = 32; // nonce length (always 32 in V3) /* * Copy in the nonce. */ System.arraycopy(v3Msg, 2, buf, count, 32); count += 32; /* * Set the length of the message. */ count -= 2; // don't include length field itself buf[0] = (byte)(count >>> 8); buf[0] |= 0x80; buf[1] = (byte)(count); count += 2; } /* * Mappings from V3 cipher suite encodings to their pure V2 equivalents. * This is taken from the SSL V3 specification, Appendix E. */ private static int[] V3toV2CipherMap1 = {-1, -1, -1, 0x02, 0x01, -1, 0x04, 0x05, -1, 0x06, 0x07}; private static int[] V3toV2CipherMap3 = {-1, -1, -1, 0x80, 0x80, -1, 0x80, 0x80, -1, 0x40, 0xC0}; /* * See which matching pure-V2 cipher specs we need to include. * We are including these not because we are actually prepared * to talk V2 but because the Oracle Web Server insists on receiving * at least 1 "pure V2" cipher suite that it supports and returns an * illegal_parameter alert unless one is present. Rather than mindlessly * claiming to implement all documented pure V2 cipher suites the code below * just claims to implement the V2 cipher suite that is "equivalent" * in terms of cipher algorithm & exportability with the actual V3 cipher * suite that we do support. */ private int V3toV2CipherSuite(byte byte1, byte byte2) { buf[count++] = 0; buf[count++] = byte1; buf[count++] = byte2; if (((byte2 & 0xff) > 0xA) || (V3toV2CipherMap1[byte2] == -1)) { return 3; } buf[count++] = (byte)V3toV2CipherMap1[byte2]; buf[count++] = 0; buf[count++] = (byte)V3toV2CipherMap3[byte2]; return 6; } }