|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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 { |
|
// |
|
// The packet should be a complete record, or more. |
|
|
|
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); |
|
} |
|
|
|
// |
|
// Check for upper bound. |
|
// |
|
|
|
if (contentLen < 0 || contentLen > maxLargeRecordSize - headerSize) { |
|
throw new SSLProtocolException( |
|
"Bad input record size, TLSCiphertext.length = " + contentLen); |
|
} |
|
|
|
// |
|
// Decrypt the fragment |
|
|
|
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); |
|
} |
|
|
|
// |
|
// check for handshake fragment |
|
|
|
if (contentType != ContentType.HANDSHAKE.id && |
|
handshakeBuffer != null && handshakeBuffer.hasRemaining()) { |
|
throw new SSLProtocolException( |
|
"Expecting a handshake fragment, but received " + |
|
ContentType.nameOf(contentType)); |
|
} |
|
|
|
// |
|
// parse handshake messages |
|
|
|
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 { |
|
// |
|
// 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); |
|
|
|
|
|
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!"); |
|
} |
|
|
|
// hack code, the exception is caught in SSLEngineImpl |
|
|
|
throw new UnsupportedOperationException( |
|
"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); |
|
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"); |
|
} |
|
} |
|
} |