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