/*
* 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 SSLEngine
. /**
* {@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 + 4)) { // 4: handshake header
// 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");
}
}
}