|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.EOFException; |
|
import java.io.InterruptedIOException; |
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
import java.io.OutputStream; |
|
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 SSLSocketInputRecord extends InputRecord implements SSLRecord { |
|
private InputStream is = null; |
|
private OutputStream os = null; |
|
private final byte[] header = new byte[headerSize]; |
|
private int headerOff = 0; |
|
|
|
private ByteBuffer recordBody = ByteBuffer.allocate(1024); |
|
|
|
private boolean formatVerified = false; |
|
|
|
|
|
private ByteBuffer handshakeBuffer = null; |
|
|
|
SSLSocketInputRecord(HandshakeHash handshakeHash) { |
|
super(handshakeHash, SSLReadCipher.nullTlsReadCipher()); |
|
} |
|
|
|
@Override |
|
int bytesInCompletePacket() throws IOException { |
|
|
|
try { |
|
readHeader(); |
|
} catch (EOFException eofe) { |
|
|
|
return -1; |
|
} |
|
|
|
byte byteZero = header[0]; |
|
int len; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (formatVerified || |
|
(byteZero == ContentType.HANDSHAKE.id) || |
|
(byteZero == ContentType.ALERT.id)) { |
|
|
|
|
|
*/ |
|
if (!ProtocolVersion.isNegotiable( |
|
header[1], header[2], false, false)) { |
|
throw new SSLException("Unrecognized record version " + |
|
ProtocolVersion.nameOf(header[1], header[2]) + |
|
" , plaintext connection?"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
formatVerified = true; |
|
|
|
|
|
|
|
*/ |
|
len = ((header[3] & 0xFF) << 8) + |
|
(header[4] & 0xFF) + headerSize; |
|
} else { |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
boolean isShort = ((byteZero & 0x80) != 0); |
|
|
|
if (isShort && ((header[2] == 1) || (header[2] == 4))) { |
|
if (!ProtocolVersion.isNegotiable( |
|
header[3], header[4], false, false)) { |
|
throw new SSLException("Unrecognized record version " + |
|
ProtocolVersion.nameOf(header[3], header[4]) + |
|
" , plaintext connection?"); |
|
} |
|
|
|
/* |
|
* 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) + |
|
// (header[1] & 0xFF) + (isShort ? 2 : 3); |
|
|
|
len = ((byteZero & 0x7F) << 8) + (header[1] & 0xFF) + 2; |
|
} else { |
|
|
|
throw new SSLException( |
|
"Unrecognized SSL message, plaintext connection?"); |
|
} |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
@Override |
|
Plaintext[] decode(ByteBuffer[] srcs, int srcsOffset, |
|
int srcsLength) throws IOException, BadPaddingException { |
|
|
|
if (isClosed) { |
|
return null; |
|
} |
|
|
|
|
|
readHeader(); |
|
|
|
Plaintext[] plaintext = null; |
|
boolean cleanInBuffer = true; |
|
try { |
|
if (!formatVerified) { |
|
formatVerified = true; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if ((header[0] != ContentType.HANDSHAKE.id) && |
|
(header[0] != ContentType.ALERT.id)) { |
|
plaintext = handleUnknownRecord(); |
|
} |
|
} |
|
|
|
|
|
if (plaintext == null) { |
|
plaintext = decodeInputRecord(); |
|
} |
|
} catch(InterruptedIOException e) { |
|
|
|
cleanInBuffer = false; |
|
throw e; |
|
} finally { |
|
if (cleanInBuffer) { |
|
headerOff = 0; |
|
recordBody.clear(); |
|
} |
|
} |
|
return plaintext; |
|
} |
|
|
|
@Override |
|
void setReceiverStream(InputStream inputStream) { |
|
this.is = inputStream; |
|
} |
|
|
|
@Override |
|
void setDeliverStream(OutputStream outputStream) { |
|
this.os = outputStream; |
|
} |
|
|
|
private Plaintext[] decodeInputRecord() throws IOException, BadPaddingException { |
|
byte contentType = header[0]; |
|
byte majorVersion = header[1]; |
|
byte minorVersion = header[2]; |
|
int contentLen = ((header[3] & 0xFF) << 8) + |
|
(header[4] & 0xFF); |
|
|
|
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); |
|
} |
|
|
|
// |
|
// Read a complete record and store in the recordBody |
|
// recordBody is used to cache incoming record and restore in case of |
|
// read operation timedout |
|
|
|
if (recordBody.position() == 0) { |
|
if (recordBody.capacity() < contentLen) { |
|
recordBody = ByteBuffer.allocate(contentLen); |
|
} |
|
recordBody.limit(contentLen); |
|
} else { |
|
contentLen = recordBody.remaining(); |
|
} |
|
readFully(contentLen); |
|
recordBody.flip(); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("record")) { |
|
SSLLogger.fine( |
|
"READ: " + |
|
ProtocolVersion.nameOf(majorVersion, minorVersion) + |
|
" " + ContentType.nameOf(contentType) + ", length = " + |
|
recordBody.remaining()); |
|
} |
|
|
|
// |
|
// Decrypt the fragment |
|
|
|
ByteBuffer fragment; |
|
try { |
|
Plaintext plaintext = |
|
readCipher.decrypt(contentType, recordBody, null); |
|
fragment = plaintext.fragment; |
|
contentType = plaintext.contentType; |
|
} catch (BadPaddingException bpe) { |
|
throw bpe; |
|
} catch (GeneralSecurityException gse) { |
|
throw (SSLProtocolException)(new SSLProtocolException( |
|
"Unexpected exception")).initCause(gse); |
|
} |
|
|
|
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(); |
|
if (!SSLHandshake.isKnown(handshakeType)) { |
|
throw new SSLProtocolException( |
|
"Unknown handshake type size, Handshake.msg_type = " + |
|
(handshakeType & 0xFF)); |
|
} |
|
|
|
int handshakeBodyLen = Record.getInt24(handshakeFrag); |
|
if (handshakeBodyLen > SSLConfiguration.maxHandshakeMessageSize) { |
|
throw new SSLProtocolException( |
|
"The size of the handshake message (" |
|
+ handshakeBodyLen |
|
+ ") exceeds the maximum allowed size (" |
|
+ SSLConfiguration.maxHandshakeMessageSize |
|
+ ")"); |
|
} |
|
|
|
handshakeFrag.reset(); |
|
int handshakeMessageLen = |
|
handshakeHeaderSize + handshakeBodyLen; |
|
if (remaining < handshakeMessageLen) { |
|
handshakeBuffer = ByteBuffer.wrap(new byte[remaining]); |
|
handshakeBuffer.put(handshakeFrag); |
|
handshakeBuffer.rewind(); |
|
break; |
|
} |
|
|
|
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() throws IOException, BadPaddingException { |
|
byte firstByte = header[0]; |
|
byte thirdByte = header[2]; |
|
|
|
|
|
if (((firstByte & 0x80) != 0) && (thirdByte == 1)) { |
|
|
|
|
|
*/ |
|
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.) |
|
*/ |
|
os.write(SSLRecord.v2NoCipher); |
|
|
|
if (SSLLogger.isOn) { |
|
if (SSLLogger.isOn("record")) { |
|
SSLLogger.fine( |
|
"Requested to negotiate unsupported SSLv2!"); |
|
} |
|
|
|
if (SSLLogger.isOn("packet")) { |
|
SSLLogger.fine("Raw write", SSLRecord.v2NoCipher); |
|
} |
|
} |
|
|
|
throw new SSLException("Unsupported SSL v2.0 ClientHello"); |
|
} |
|
|
|
int msgLen = ((header[0] & 0x7F) << 8) | (header[1] & 0xFF); |
|
if (recordBody.position() == 0) { |
|
if (recordBody.capacity() < (headerSize + msgLen)) { |
|
recordBody = ByteBuffer.allocate(headerSize + msgLen); |
|
} |
|
recordBody.limit(headerSize + msgLen); |
|
recordBody.put(header, 0, headerSize); |
|
} else { |
|
msgLen = recordBody.remaining(); |
|
} |
|
msgLen -= 3; |
|
readFully(msgLen); |
|
recordBody.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. |
|
*/ |
|
recordBody.position(2); |
|
handshakeHash.receive(recordBody); |
|
recordBody.position(0); |
|
|
|
ByteBuffer converted = convertToClientHello(recordBody); |
|
|
|
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"); |
|
} |
|
} |
|
|
|
|
|
private int readFully(int len) throws IOException { |
|
int end = len + recordBody.position(); |
|
int off = recordBody.position(); |
|
try { |
|
while (off < end) { |
|
off += read(is, recordBody.array(), off, end - off); |
|
} |
|
} finally { |
|
recordBody.position(off); |
|
} |
|
return len; |
|
} |
|
|
|
|
|
private int readHeader() throws IOException { |
|
while (headerOff < headerSize) { |
|
headerOff += read(is, header, headerOff, headerSize - headerOff); |
|
} |
|
return headerSize; |
|
} |
|
|
|
private static int read(InputStream is, byte[] buf, int off, int len) throws IOException { |
|
int readLen = is.read(buf, off, len); |
|
if (readLen < 0) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("packet")) { |
|
SSLLogger.fine("Raw read: EOF"); |
|
} |
|
throw new EOFException("SSL peer shut down incorrectly"); |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("packet")) { |
|
ByteBuffer bb = ByteBuffer.wrap(buf, off, readLen); |
|
SSLLogger.fine("Raw read", bb); |
|
} |
|
return readLen; |
|
} |
|
|
|
|
|
void deplete(boolean tryToRead) throws IOException { |
|
int remaining = is.available(); |
|
if (tryToRead && (remaining == 0)) { |
|
|
|
is.read(); |
|
} |
|
|
|
while ((remaining = is.available()) != 0) { |
|
is.skip(remaining); |
|
} |
|
} |
|
} |