package sun.security.ssl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import javax.crypto.BadPaddingException;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLProtocolException;
import sun.security.ssl.SSLCipher.SSLReadCipher;
final class SSLEngineInputRecord extends InputRecord implements SSLRecord {
private boolean formatVerified = false;
private ByteBuffer handshakeBuffer = null;
SSLEngineInputRecord(HandshakeHash handshakeHash) {
super(handshakeHash, SSLReadCipher.nullTlsReadCipher());
}
@Override
int estimateFragmentSize(int packetSize) {
if (packetSize > 0) {
return readCipher.estimateFragmentSize(packetSize, headerSize);
} else {
return Record.maxDataSize;
}
}
@Override
int bytesInCompletePacket(
ByteBuffer[] srcs, int srcsOffset, int srcsLength) throws IOException {
return bytesInCompletePacket(srcs[srcsOffset]);
}
private int bytesInCompletePacket(ByteBuffer packet) throws SSLException {
if (packet.remaining() < 5) {
return -1;
}
int pos = packet.position();
byte byteZero = packet.get(pos);
int len = 0;
if (formatVerified ||
(byteZero == ContentType.HANDSHAKE.id) ||
(byteZero == ContentType.ALERT.id)) {
byte majorVersion = packet.get(pos + 1);
byte minorVersion = packet.get(pos + 2);
if (!ProtocolVersion.isNegotiable(
majorVersion, minorVersion, false, false)) {
throw new SSLException("Unrecognized record version " +
ProtocolVersion.nameOf(majorVersion, minorVersion) +
" , plaintext connection?");
}
formatVerified = true;
len = ((packet.get(pos + 3) & 0xFF) << 8) +
(packet.get(pos + 4) & 0xFF) + headerSize;
} else {
boolean isShort = ((byteZero & 0x80) != 0);
if (isShort &&
((packet.get(pos + 2) == 1) || packet.get(pos + 2) == 4)) {
byte majorVersion = packet.get(pos + 3);
byte minorVersion = packet.get(pos + 4);
if (!ProtocolVersion.isNegotiable(
majorVersion, minorVersion, false, false)) {
throw new SSLException("Unrecognized record version " +
ProtocolVersion.nameOf(majorVersion, minorVersion) +
" , plaintext connection?");
}
int mask = (isShort ? 0x7F : 0x3F);
len = ((byteZero & mask) << 8) +
(packet.get(pos + 1) & 0xFF) + (isShort ? 2 : 3);
} else {
throw new SSLException(
"Unrecognized SSL message, plaintext connection?");
}
}
return len;
}
@Override
Plaintext[] decode(ByteBuffer[] srcs, int srcsOffset,
int srcsLength) throws IOException, BadPaddingException {
if (srcs == null || srcs.length == 0 || srcsLength == 0) {
return new Plaintext[0];
} else if (srcsLength == 1) {
return decode(srcs[srcsOffset]);
} else {
ByteBuffer packet = extract(srcs,
srcsOffset, srcsLength, SSLRecord.headerSize);
return decode(packet);
}
}
private Plaintext[] decode(ByteBuffer packet)
throws IOException, BadPaddingException {
if (isClosed) {
return null;
}
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine("Raw read", packet);
}
if (!formatVerified) {
formatVerified = true;
int pos = packet.position();
byte byteZero = packet.get(pos);
if (byteZero != ContentType.HANDSHAKE.id &&
byteZero != ContentType.ALERT.id) {
return handleUnknownRecord(packet);
}
}
return decodeInputRecord(packet);
}
private Plaintext[] decodeInputRecord(ByteBuffer packet)
throws IOException, BadPaddingException {
int srcPos = packet.position();
int srcLim = packet.limit();
byte contentType = packet.get();
byte majorVersion = packet.get();
byte minorVersion = packet.get();
int contentLen = Record.getInt16(packet);
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine(
"READ: " +
ProtocolVersion.nameOf(majorVersion, minorVersion) +
" " + ContentType.nameOf(contentType) + ", length = " +
contentLen);
}
if (contentLen < 0 || contentLen > maxLargeRecordSize - headerSize) {
throw new SSLProtocolException(
"Bad input record size, TLSCiphertext.length = " + contentLen);
}
int recLim = srcPos + SSLRecord.headerSize + contentLen;
packet.limit(recLim);
packet.position(srcPos + SSLRecord.headerSize);
ByteBuffer fragment;
try {
Plaintext plaintext =
readCipher.decrypt(contentType, packet, null);
fragment = plaintext.fragment;
contentType = plaintext.contentType;
} catch (BadPaddingException bpe) {
throw bpe;
} catch (GeneralSecurityException gse) {
throw (SSLProtocolException)(new SSLProtocolException(
"Unexpected exception")).initCause(gse);
} finally {
packet.limit(srcLim);
packet.position(recLim);
}
if (contentType != ContentType.HANDSHAKE.id &&
handshakeBuffer != null && handshakeBuffer.hasRemaining()) {
throw new SSLProtocolException(
"Expecting a handshake fragment, but received " +
ContentType.nameOf(contentType));
}
if (contentType == ContentType.HANDSHAKE.id) {
ByteBuffer handshakeFrag = fragment;
if ((handshakeBuffer != null) &&
(handshakeBuffer.remaining() != 0)) {
ByteBuffer bb = ByteBuffer.wrap(new byte[
handshakeBuffer.remaining() + fragment.remaining()]);
bb.put(handshakeBuffer);
bb.put(fragment);
handshakeFrag = bb.rewind();
handshakeBuffer = null;
}
ArrayList<Plaintext> plaintexts = new ArrayList<>(5);
while (handshakeFrag.hasRemaining()) {
int remaining = handshakeFrag.remaining();
if (remaining < handshakeHeaderSize) {
handshakeBuffer = ByteBuffer.wrap(new byte[remaining]);
handshakeBuffer.put(handshakeFrag);
handshakeBuffer.rewind();
break;
}
handshakeFrag.mark();
byte handshakeType = handshakeFrag.get();
int handshakeBodyLen = Record.getInt24(handshakeFrag);
handshakeFrag.reset();
int handshakeMessageLen =
handshakeHeaderSize + handshakeBodyLen;
if (remaining < handshakeMessageLen) {
handshakeBuffer = ByteBuffer.wrap(new byte[remaining]);
handshakeBuffer.put(handshakeFrag);
handshakeBuffer.rewind();
break;
} else if (remaining == handshakeMessageLen) {
if (handshakeHash.isHashable(handshakeType)) {
handshakeHash.receive(handshakeFrag);
}
plaintexts.add(
new Plaintext(contentType,
majorVersion, minorVersion, -1, -1L, handshakeFrag)
);
break;
} else {
int fragPos = handshakeFrag.position();
int fragLim = handshakeFrag.limit();
int nextPos = fragPos + handshakeMessageLen;
handshakeFrag.limit(nextPos);
if (handshakeHash.isHashable(handshakeType)) {
handshakeHash.receive(handshakeFrag);
}
plaintexts.add(
new Plaintext(contentType, majorVersion, minorVersion,
-1, -1L, handshakeFrag.slice())
);
handshakeFrag.position(nextPos);
handshakeFrag.limit(fragLim);
}
}
return plaintexts.toArray(new Plaintext[0]);
}
return new Plaintext[] {
new Plaintext(contentType,
majorVersion, minorVersion, -1, -1L, fragment)
};
}
private Plaintext[] handleUnknownRecord(ByteBuffer packet)
throws IOException, BadPaddingException {
int srcPos = packet.position();
int srcLim = packet.limit();
byte firstByte = packet.get(srcPos);
byte thirdByte = packet.get(srcPos + 2);
if (((firstByte & 0x80) != 0) && (thirdByte == 1)) {
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)) {
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine(
"Requested to negotiate unsupported SSLv2!");
}
throw new UnsupportedOperationException(
"Unsupported SSL v2.0 ClientHello");
}
packet.position(srcPos + 2);
handshakeHash.receive(packet);
packet.position(srcPos);
ByteBuffer converted = convertToClientHello(packet);
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine(
"[Converted] ClientHello", converted);
}
return new Plaintext[] {
new Plaintext(ContentType.HANDSHAKE.id,
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");
}
}
}