src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java
author erikj
Tue, 12 Sep 2017 19:03:39 +0200
changeset 47216 71c04702a3d5
parent 34687 jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java@d302ed125dc9
child 50768 68fa3d4026ea
child 56542 56aaa6cb3693
permissions -rw-r--r--
8187443: Forest Consolidation: Move files to unified layout Reviewed-by: darcy, ihse

/*
 * 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 javax.net.ssl.SSLHandshakeException;
import sun.security.util.HexDumpEncoder;


/**
 * {@code OutputRecord} implementation for {@code SSLSocket}.
 */
final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
    private OutputStream deliverStream = null;

    SSLSocketOutputRecord() {
        this.writeAuthenticator = MAC.TLS_NULL;

        this.packetSize = SSLRecord.maxRecordSize;
        this.protocolVersion = ProtocolVersion.DEFAULT_TLS;
    }

    @Override
    void encodeAlert(byte level, byte description) throws IOException {
        // use the buf of ByteArrayOutputStream
        int position = headerSize + writeCipher.getExplicitNonceSize();
        count = position;

        write(level);
        write(description);

        if (debug != null && Debug.isOn("record")) {
            System.out.println(Thread.currentThread().getName() +
                    ", WRITE: " + protocolVersion +
                    " " + Record.contentName(Record.ct_alert) +
                    ", length = " + (count - headerSize));
        }

        // Encrypt the fragment and wrap up a record.
        encrypt(writeAuthenticator, writeCipher,
                Record.ct_alert, headerSize);

        // deliver this message
        deliverStream.write(buf, 0, count);    // may throw IOException
        deliverStream.flush();                 // may throw IOException

        if (debug != null && Debug.isOn("packet")) {
             Debug.printHex(
                    "[Raw write]: length = " + count, buf, 0, count);
        }

        // reset the internal buffer
        count = 0;
    }

    @Override
    void encodeHandshake(byte[] source,
            int offset, int length) throws IOException {

        if (firstMessage) {
            firstMessage = false;

            if ((helloVersion == ProtocolVersion.SSL20Hello) &&
                (source[offset] == HandshakeMessage.ht_client_hello) &&
                                            //  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.update(record, 2, (limit - 2));

                if (debug != null && Debug.isOn("record")) {
                     System.out.println(Thread.currentThread().getName() +
                        ", 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 (debug != null && Debug.isOn("packet")) {
                     Debug.printHex(
                            "[Raw write]: length = " + count, record, 0, limit);
                }

                return;
            }
        }

        byte handshakeType = source[0];
        if (handshakeType != HandshakeMessage.ht_hello_request) {
            handshakeHash.update(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 (debug != null && Debug.isOn("record")) {
                System.out.println(Thread.currentThread().getName() +
                        ", WRITE: " + protocolVersion +
                        " " + Record.contentName(Record.ct_handshake) +
                        ", length = " + (count - headerSize));
            }

            // Encrypt the fragment and wrap up a record.
            encrypt(writeAuthenticator, writeCipher,
                    Record.ct_handshake, headerSize);

            // deliver this message
            deliverStream.write(buf, 0, count);    // may throw IOException
            deliverStream.flush();                 // may throw IOException

            if (debug != null && Debug.isOn("packet")) {
                 Debug.printHex(
                        "[Raw write]: length = " + count, buf, 0, count);
            }

            // reset the offset
            offset += fragLen;

            // reset the internal buffer
            count = position;
        }
    }

    @Override
    void encodeChangeCipherSpec() throws IOException {

        // use the buf of ByteArrayOutputStream
        int position = headerSize + writeCipher.getExplicitNonceSize();
        count = position;

        write((byte)1);         // byte 1: change_cipher_spec(

        if (debug != null && Debug.isOn("record")) {
            System.out.println(Thread.currentThread().getName() +
                    ", WRITE: " + protocolVersion +
                    " " + Record.contentName(Record.ct_change_cipher_spec) +
                    ", length = " + (count - headerSize));
        }

        // Encrypt the fragment and wrap up a record.
        encrypt(writeAuthenticator, writeCipher,
                Record.ct_change_cipher_spec, headerSize);

        // deliver this message
        deliverStream.write(buf, 0, count);        // may throw IOException
        // deliverStream.flush();                  // flush in Finished

        if (debug != null && Debug.isOn("packet")) {
             Debug.printHex(
                    "[Raw write]: length = " + count, buf, 0, count);
        }

        // reset the internal buffer
        count = 0;
    }

    @Override
    public void flush() throws IOException {
        int position = headerSize + writeCipher.getExplicitNonceSize();
        if (count <= position) {
            return;
        }

        if (debug != null && Debug.isOn("record")) {
            System.out.println(Thread.currentThread().getName() +
                    ", WRITE: " + protocolVersion +
                    " " + Record.contentName(Record.ct_handshake) +
                    ", length = " + (count - headerSize));
        }

        // Encrypt the fragment and wrap up a record.
        encrypt(writeAuthenticator, writeCipher,
                    Record.ct_handshake, headerSize);

        // deliver this message
        deliverStream.write(buf, 0, count);    // may throw IOException
        deliverStream.flush();                 // may throw IOException

        if (debug != null && Debug.isOn("packet")) {
             Debug.printHex(
                    "[Raw write]: length = " + count, buf, 0, count);
        }

        // reset the internal buffer
        count = 0;      // DON'T use position
    }

    @Override
    void deliver(byte[] source, int offset, int length) throws IOException {

        if (writeAuthenticator.seqNumOverflow()) {
            if (debug != null && Debug.isOn("ssl")) {
                System.out.println(Thread.currentThread().getName() +
                    ", 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 macLen = 0;
            if (writeAuthenticator instanceof MAC) {
                macLen = ((MAC)writeAuthenticator).MAClen();
            }

            int fragLen;
            if (packetSize > 0) {
                fragLen = Math.min(maxRecordSize, packetSize);
                fragLen = writeCipher.calculateFragmentSize(
                        fragLen, macLen, headerSize);

                fragLen = Math.min(fragLen, Record.maxDataSize);
            } else {
                fragLen = Record.maxDataSize;
            }

            if (fragmentSize > 0) {
                fragLen = Math.min(fragLen, fragmentSize);
            }

            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 (debug != null && Debug.isOn("record")) {
                System.out.println(Thread.currentThread().getName() +
                        ", WRITE: " + protocolVersion +
                        " " + Record.contentName(Record.ct_application_data) +
                        ", length = " + (count - headerSize));
            }

            // Encrypt the fragment and wrap up a record.
            encrypt(writeAuthenticator, writeCipher,
                    Record.ct_application_data, headerSize);

            // deliver this message
            deliverStream.write(buf, 0, count);    // may throw IOException
            deliverStream.flush();                 // may throw IOException

            if (debug != null && Debug.isOn("packet")) {
                 Debug.printHex(
                        "[Raw write]: length = " + count, buf, 0, count);
            }

            // reset the internal buffer
            count = 0;

            if (isFirstAppOutputRecord) {
                isFirstAppOutputRecord = false;
            }

            offset += fragLen;
        }
    }

    @Override
    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.
     */
    boolean needToSplitPayload() {
        return (!protocolVersion.useTLS11PlusSpec()) &&
                writeCipher.isCBCMode() && !isFirstAppOutputRecord &&
                Record.enableCBCProtection;
    }

    private int getFragLimit() {
        int macLen = 0;
        if (writeAuthenticator instanceof MAC) {
            macLen = ((MAC)writeAuthenticator).MAClen();
        }

        int fragLimit;
        if (packetSize > 0) {
            fragLimit = Math.min(maxRecordSize, packetSize);
            fragLimit = writeCipher.calculateFragmentSize(
                    fragLimit, macLen, headerSize);

            fragLimit = Math.min(fragLimit, Record.maxDataSize);
        } else {
            fragLimit = Record.maxDataSize;
        }

        if (fragmentSize > 0) {
            fragLimit = Math.min(fragLimit, fragmentSize);
        }

        return fragLimit;
    }
}