/*
 * Copyright (c) 1996, 2014, 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 javax.crypto.BadPaddingException;

import javax.net.ssl.*;

import sun.security.util.HexDumpEncoder;


InputRecord implementation for SSLSocket.
Author:David Brownell
/** * {@code InputRecord} implementation for {@code SSLSocket}. * * @author David Brownell */
final class SSLSocketInputRecord extends InputRecord implements SSLRecord { private OutputStream deliverStream = null; private byte[] temporary = new byte[1024]; // used by handshake hash computation for handshake fragment private byte prevType = -1; private int hsMsgOff = 0; private int hsMsgLen = 0; private boolean formatVerified = false; // SSLv2 ruled out? private boolean hasHeader = false; // Had read the record header SSLSocketInputRecord() { this.readAuthenticator = MAC.TLS_NULL; } @Override int bytesInCompletePacket(InputStream is) throws IOException { if (!hasHeader) { // read exactly one record int really = read(is, temporary, 0, headerSize); if (really < 0) { throw new EOFException("SSL peer shut down incorrectly"); } hasHeader = true; } byte byteZero = temporary[0]; int len = 0; /* * If we have already verified previous packets, we can * ignore the verifications steps, and jump right to the * determination. Otherwise, try one last hueristic to * see if it's SSL/TLS. */ if (formatVerified || (byteZero == ct_handshake) || (byteZero == ct_alert)) { /* * Last sanity check that it's not a wild record */ ProtocolVersion recordVersion = ProtocolVersion.valueOf(temporary[1], temporary[2]); // check the record version checkRecordVersion(recordVersion, false); /* * Reasonably sure this is a V3, disable further checks. * We can't do the same in the v2 check below, because * read still needs to parse/handle the v2 clientHello. */ formatVerified = true; /* * One of the SSLv3/TLS message types. */ len = ((temporary[3] & 0xFF) << 8) + (temporary[4] & 0xFF) + headerSize; } else { /* * Must be SSLv2 or something unknown. * Check if it's short (2 bytes) or * long (3) header. * * Internals can warn about unsupported SSLv2 */ boolean isShort = ((byteZero & 0x80) != 0); if (isShort && ((temporary[2] == 1) || (temporary[2] == 4))) { ProtocolVersion recordVersion = ProtocolVersion.valueOf(temporary[3], temporary[4]); // check the record version checkRecordVersion(recordVersion, true); /* * Client or Server Hello */ // // Short header is using here. We reverse the code here // in case it is used in the future. // // int mask = (isShort ? 0x7F : 0x3F); // len = ((byteZero & mask) << 8) + // (temporary[1] & 0xFF) + (isShort ? 2 : 3); // len = ((byteZero & 0x7F) << 8) + (temporary[1] & 0xFF) + 2; } else { // Gobblygook! throw new SSLException( "Unrecognized SSL message, plaintext connection?"); } } return len; } // destination.position() is zero. @Override Plaintext decode(InputStream is, ByteBuffer destination) throws IOException, BadPaddingException { if (isClosed) { return null; } if (!hasHeader) { // read exactly one record int really = read(is, temporary, 0, headerSize); if (really < 0) { throw new EOFException("SSL peer shut down incorrectly"); } hasHeader = true; } Plaintext plaintext = null; if (!formatVerified) { formatVerified = true; /* * The first record must either be a handshake record or an * alert message. If it's not, it is either invalid or an * SSLv2 message. */ if ((temporary[0] != ct_handshake) && (temporary[0] != ct_alert)) { plaintext = handleUnknownRecord(is, temporary, destination); } } if (plaintext == null) { plaintext = decodeInputRecord(is, temporary, destination); } // The record header should has comsumed. hasHeader = false; return plaintext; } @Override void setDeliverStream(OutputStream outputStream) { this.deliverStream = outputStream; } // Note that destination may be null private Plaintext decodeInputRecord(InputStream is, byte[] header, ByteBuffer destination) throws IOException, BadPaddingException { byte contentType = header[0]; byte majorVersion = header[1]; byte minorVersion = header[2]; int contentLen = ((header[3] & 0xFF) << 8) + (header[4] & 0xFF); // // Check for upper bound. // // Note: May check packetSize limit in the future. if (contentLen < 0 || contentLen > maxLargeRecordSize - headerSize) { throw new SSLProtocolException( "Bad input record size, TLSCiphertext.length = " + contentLen); } // // Read a complete record. // if (destination == null) { destination = ByteBuffer.allocate(headerSize + contentLen); } // Otherwise, the destination buffer should have enough room. int dstPos = destination.position(); destination.put(temporary, 0, headerSize); while (contentLen > 0) { int howmuch = Math.min(temporary.length, contentLen); int really = read(is, temporary, 0, howmuch); if (really < 0) { throw new EOFException("SSL peer shut down incorrectly"); } destination.put(temporary, 0, howmuch); contentLen -= howmuch; } destination.flip(); destination.position(dstPos + headerSize); if (debug != null && Debug.isOn("record")) { System.out.println(Thread.currentThread().getName() + ", READ: " + ProtocolVersion.valueOf(majorVersion, minorVersion) + " " + Record.contentName(contentType) + ", length = " + destination.remaining()); } // // Decrypt the fragment // ByteBuffer plaintext = decrypt(readAuthenticator, readCipher, contentType, destination); if ((contentType != ct_handshake) && (hsMsgOff != hsMsgLen)) { throw new SSLProtocolException( "Expected to get a handshake fragment"); } // // handshake hashing // if (contentType == ct_handshake) { int pltPos = plaintext.position(); int pltLim = plaintext.limit(); int frgPos = pltPos; for (int remains = plaintext.remaining(); remains > 0;) { int howmuch; byte handshakeType; if (hsMsgOff < hsMsgLen) { // a fragment of the handshake message howmuch = Math.min((hsMsgLen - hsMsgOff), remains); handshakeType = prevType; hsMsgOff += howmuch; if (hsMsgOff == hsMsgLen) { // Now is a complete handshake message. hsMsgOff = 0; hsMsgLen = 0; } } else { // hsMsgOff == hsMsgLen, a new handshake message handshakeType = plaintext.get(); int handshakeLen = ((plaintext.get() & 0xFF) << 16) | ((plaintext.get() & 0xFF) << 8) | (plaintext.get() & 0xFF); plaintext.position(frgPos); if (remains < (handshakeLen + 1)) { // 1: handshake type // This handshake message is fragmented. prevType = handshakeType; hsMsgOff = remains - 4; // 4: handshake header hsMsgLen = handshakeLen; } howmuch = Math.min(handshakeLen + 4, remains); } plaintext.limit(frgPos + howmuch); if (handshakeType == HandshakeMessage.ht_hello_request) { // omitted from handshake hash computation } else if ((handshakeType != HandshakeMessage.ht_finished) && (handshakeType != HandshakeMessage.ht_certificate_verify)) { if (handshakeHash == null) { // used for cache only handshakeHash = new HandshakeHash(false); } handshakeHash.update(plaintext); } else { // Reserve until this handshake message has been processed. if (handshakeHash == null) { // used for cache only handshakeHash = new HandshakeHash(false); } handshakeHash.reserve(plaintext); } plaintext.position(frgPos + howmuch); plaintext.limit(pltLim); frgPos += howmuch; remains -= howmuch; } plaintext.position(pltPos); } return new Plaintext(contentType, majorVersion, minorVersion, -1, -1L, plaintext); // recordEpoch, recordSeq, plaintext); } private Plaintext handleUnknownRecord(InputStream is, byte[] header, ByteBuffer destination) throws IOException, BadPaddingException { byte firstByte = header[0]; byte thirdByte = header[2]; // Does it look like a Version 2 client hello (V2ClientHello)? if (((firstByte & 0x80) != 0) && (thirdByte == 1)) { /* * If SSLv2Hello is not enabled, throw an exception. */ if (helloVersion != ProtocolVersion.SSL20Hello) { throw new SSLHandshakeException("SSLv2Hello is not enabled"); } byte majorVersion = header[3]; byte minorVersion = header[4]; if ((majorVersion == ProtocolVersion.SSL20Hello.major) && (minorVersion == ProtocolVersion.SSL20Hello.minor)) { /* * Looks like a V2 client hello, but not one saying * "let's talk SSLv3". So we need to send an SSLv2 * error message, one that's treated as fatal by * clients (Otherwise we'll hang.) */ deliverStream.write(SSLRecord.v2NoCipher); // SSLv2Hello if (debug != null) { if (Debug.isOn("record")) { System.out.println(Thread.currentThread().getName() + "Requested to negotiate unsupported SSLv2!"); } if (Debug.isOn("packet")) { Debug.printHex( "[Raw write]: length = " + SSLRecord.v2NoCipher.length, SSLRecord.v2NoCipher); } } throw new SSLException("Unsupported SSL v2.0 ClientHello"); } int msgLen = ((header[0] & 0x7F) << 8) | (header[1] & 0xFF); if (destination == null) { destination = ByteBuffer.allocate(headerSize + msgLen); } destination.put(temporary, 0, headerSize); msgLen -= 3; // had read 3 bytes of content as header while (msgLen > 0) { int howmuch = Math.min(temporary.length, msgLen); int really = read(is, temporary, 0, howmuch); if (really < 0) { throw new EOFException("SSL peer shut down incorrectly"); } destination.put(temporary, 0, howmuch); msgLen -= howmuch; } destination.flip(); /* * If we can map this into a V3 ClientHello, read and * hash the rest of the V2 handshake, turn it into a * V3 ClientHello message, and pass it up. */ destination.position(2); // exclude the header if (handshakeHash == null) { // used for cache only handshakeHash = new HandshakeHash(false); } handshakeHash.update(destination); destination.position(0); ByteBuffer converted = convertToClientHello(destination); if (debug != null && Debug.isOn("packet")) { Debug.printHex( "[Converted] ClientHello", converted); } return new Plaintext(ct_handshake, majorVersion, minorVersion, -1, -1L, converted); } else { if (((firstByte & 0x80) != 0) && (thirdByte == 4)) { throw new SSLException("SSL V2.0 servers are not supported."); } throw new SSLException("Unsupported or unrecognized SSL message"); } } // Read the exact bytes of data, otherwise, return -1. private static int read(InputStream is, byte[] buffer, int offset, int len) throws IOException { int n = 0; while (n < len) { int readLen = is.read(buffer, offset + n, len - n); if (readLen < 0) { return -1; } if (debug != null && Debug.isOn("packet")) { Debug.printHex( "[Raw read]: length = " + readLen, buffer, offset + n, readLen); } n += readLen; } return n; } }