--- /dev/null Fri May 29 00:48:55 2015 +++ new/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java Fri May 29 00:48:55 2015 @@ -0,0 +1,409 @@ +/* + * 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.misc.HexDumpEncoder; + + +/** + * {@code InputRecord} implementation for {@code SSLEngine}. + */ +final class SSLEngineInputRecord extends InputRecord implements SSLRecord { + // 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? + + SSLEngineInputRecord() { + this.readAuthenticator = MAC.TLS_NULL; + } + + @Override + int estimateFragmentSize(int packetSize) { + int macLen = 0; + if (readAuthenticator instanceof MAC) { + macLen = ((MAC)readAuthenticator).MAClen(); + } + + if (packetSize > 0) { + return readCipher.estimateFragmentSize( + packetSize, macLen, headerSize); + } else { + return Record.maxDataSize; + } + } + + @Override + int bytesInCompletePacket(ByteBuffer packet) throws SSLException { + /* + * SSLv2 length field is in bytes 0/1 + * SSLv3/TLS length field is in bytes 3/4 + */ + if (packet.remaining() < 5) { + return -1; + } + + int pos = packet.position(); + byte byteZero = packet.get(pos); + + 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( + packet.get(pos + 1), packet.get(pos + 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 = ((packet.get(pos + 3) & 0xFF) << 8) + + (packet.get(pos + 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 && + ((packet.get(pos + 2) == 1) || packet.get(pos + 2) == 4)) { + + ProtocolVersion recordVersion = ProtocolVersion.valueOf( + packet.get(pos + 3), packet.get(pos + 4)); + + // check the record version + checkRecordVersion(recordVersion, true); + + /* + * Client or Server Hello + */ + int mask = (isShort ? 0x7F : 0x3F); + len = ((byteZero & mask) << 8) + + (packet.get(pos + 1) & 0xFF) + (isShort ? 2 : 3); + + } else { + // Gobblygook! + throw new SSLException( + "Unrecognized SSL message, plaintext connection?"); + } + } + + return len; + } + + @Override + void checkRecordVersion(ProtocolVersion recordVersion, + boolean allowSSL20Hello) throws SSLException { + + if (recordVersion.maybeDTLSProtocol()) { + throw new SSLException( + "Unrecognized record version " + recordVersion + + " , DTLS packet?"); + } + + // Check if the record version is too old. + if ((recordVersion.v < ProtocolVersion.MIN.v)) { + // if it's not SSLv2, we're out of here. + if (!allowSSL20Hello || + (recordVersion.v != ProtocolVersion.SSL20Hello.v)) { + throw new SSLException( + "Unsupported record version " + recordVersion); + } + } + } + + @Override + Plaintext decode(ByteBuffer packet) + throws IOException, BadPaddingException { + + if (isClosed) { + return null; + } + + if (debug != null && Debug.isOn("packet")) { + Debug.printHex( + "[Raw read]: length = " + packet.remaining(), packet); + } + + // The caller should have validated the record. + 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. + */ + int pos = packet.position(); + byte byteZero = packet.get(pos); + if (byteZero != ct_handshake && byteZero != ct_alert) { + return handleUnknownRecord(packet); + } + } + + return decodeInputRecord(packet); + } + + private Plaintext decodeInputRecord(ByteBuffer packet) + throws IOException, BadPaddingException { + + // + // The packet should be a complete record, or more. + // + + int srcPos = packet.position(); + int srcLim = packet.limit(); + + byte contentType = packet.get(); // pos: 0 + byte majorVersion = packet.get(); // pos: 1 + byte minorVersion = packet.get(); // pos: 2 + int contentLen = ((packet.get() & 0xFF) << 8) + + (packet.get() & 0xFF); // pos: 3, 4 + + if (debug != null && Debug.isOn("record")) { + System.out.println(Thread.currentThread().getName() + + ", READ: " + + ProtocolVersion.valueOf(majorVersion, minorVersion) + + " " + Record.contentName(contentType) + ", length = " + + contentLen); + } + + // + // 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); + } + + // + // check for handshake fragment + // + if ((contentType != ct_handshake) && (hsMsgOff != hsMsgLen)) { + throw new SSLProtocolException( + "Expected to get a handshake fragment"); + } + + // + // Decrypt the fragment + // + int recLim = srcPos + SSLRecord.headerSize + contentLen; + packet.limit(recLim); + packet.position(srcPos + SSLRecord.headerSize); + + ByteBuffer plaintext; + try { + plaintext = + decrypt(readAuthenticator, readCipher, contentType, packet); + } finally { + // comsume a complete record + packet.limit(srcLim); + packet.position(recLim); + } + + // + // 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(ByteBuffer packet) + throws IOException, BadPaddingException { + + // + // The packet should be a complete record. + // + int srcPos = packet.position(); + int srcLim = packet.limit(); + + byte firstByte = packet.get(srcPos); + byte thirdByte = packet.get(srcPos + 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 = packet.get(srcPos + 3); + byte minorVersion = packet.get(srcPos + 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.) + */ + if (debug != null && Debug.isOn("record")) { + System.out.println(Thread.currentThread().getName() + + "Requested to negotiate unsupported SSLv2!"); + } + + // hack code, the exception is caught in SSLEngineImpl + // so that SSLv2 error message can be delivered properly. + throw new UnsupportedOperationException( // SSLv2Hello + "Unsupported SSL v2.0 ClientHello"); + } + + /* + * 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. + */ + packet.position(srcPos + 2); // exclude the header + + if (handshakeHash == null) { + // used for cache only + handshakeHash = new HandshakeHash(false); + } + handshakeHash.update(packet); + packet.position(srcPos); + + ByteBuffer converted = convertToClientHello(packet); + + 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"); + } + } + +}