|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.Closeable; |
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
import java.io.OutputStream; |
|
import java.nio.BufferUnderflowException; |
|
import java.nio.ByteBuffer; |
|
import java.util.concurrent.locks.ReentrantLock; |
|
import javax.crypto.BadPaddingException; |
|
import sun.security.ssl.SSLCipher.SSLReadCipher; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
abstract class InputRecord implements Record, Closeable { |
|
SSLReadCipher readCipher; |
|
|
|
TransportContext tc; |
|
|
|
final HandshakeHash handshakeHash; |
|
volatile boolean isClosed; |
|
|
|
// The ClientHello version to accept. If set to ProtocolVersion.SSL20Hello |
|
// and the first message we read is a ClientHello in V2 format, we convert |
|
|
|
ProtocolVersion helloVersion; |
|
|
|
|
|
int fragmentSize; |
|
|
|
final ReentrantLock recordLock = new ReentrantLock(); |
|
|
|
InputRecord(HandshakeHash handshakeHash, SSLReadCipher readCipher) { |
|
this.readCipher = readCipher; |
|
this.helloVersion = ProtocolVersion.TLS10; |
|
this.handshakeHash = handshakeHash; |
|
this.isClosed = false; |
|
this.fragmentSize = Record.maxDataSize; |
|
} |
|
|
|
void setHelloVersion(ProtocolVersion helloVersion) { |
|
this.helloVersion = helloVersion; |
|
} |
|
|
|
boolean seqNumIsHuge() { |
|
return (readCipher.authenticator != null) && |
|
readCipher.authenticator.seqNumIsHuge(); |
|
} |
|
|
|
boolean isEmpty() { |
|
return false; |
|
} |
|
|
|
|
|
void expectingFinishFlight() { |
|
// blank |
|
} |
|
|
|
|
|
void finishHandshake() { |
|
// blank |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public void close() throws IOException { |
|
recordLock.lock(); |
|
try { |
|
if (!isClosed) { |
|
isClosed = true; |
|
readCipher.dispose(); |
|
} |
|
} finally { |
|
recordLock.unlock(); |
|
} |
|
} |
|
|
|
boolean isClosed() { |
|
return isClosed; |
|
} |
|
|
|
|
|
void changeReadCiphers(SSLReadCipher readCipher) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
readCipher.dispose(); |
|
|
|
this.readCipher = readCipher; |
|
} |
|
|
|
|
|
void changeFragmentSize(int fragmentSize) { |
|
this.fragmentSize = fragmentSize; |
|
} |
|
|
|
/* |
|
* Check if there is enough inbound data in the ByteBuffer to make |
|
* a inbound packet. |
|
* |
|
* @return -1 if there are not enough bytes to tell (small header), |
|
*/ |
|
|
|
int bytesInCompletePacket( |
|
ByteBuffer[] srcs, int srcsOffset, int srcsLength) throws IOException { |
|
|
|
throw new UnsupportedOperationException("Not supported yet."); |
|
} |
|
|
|
|
|
int bytesInCompletePacket() throws IOException { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
|
|
void setReceiverStream(InputStream inputStream) { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
|
|
Plaintext acquirePlaintext() |
|
throws IOException, BadPaddingException { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
// read, decrypt and decompress the network record. |
|
|
|
abstract Plaintext[] decode(ByteBuffer[] srcs, int srcsOffset, |
|
int srcsLength) throws IOException, BadPaddingException; |
|
|
|
|
|
void setDeliverStream(OutputStream outputStream) { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
// calculate plaintext fragment size |
|
// |
|
|
|
int estimateFragmentSize(int packetSize) { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
// |
|
// shared helpers |
|
// |
|
|
|
|
|
static ByteBuffer convertToClientHello(ByteBuffer packet) { |
|
int srcPos = packet.position(); |
|
|
|
byte firstByte = packet.get(); |
|
byte secondByte = packet.get(); |
|
int recordLen = (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2; |
|
|
|
packet.position(srcPos + 3); |
|
|
|
byte majorVersion = packet.get(); |
|
byte minorVersion = packet.get(); |
|
|
|
int cipherSpecLen = ((packet.get() & 0xFF) << 8) + |
|
(packet.get() & 0xFF); |
|
int sessionIdLen = ((packet.get() & 0xFF) << 8) + |
|
(packet.get() & 0xFF); |
|
int nonceLen = ((packet.get() & 0xFF) << 8) + |
|
(packet.get() & 0xFF); |
|
|
|
// Required space for the target SSLv3 ClientHello message. |
|
// 5: record header size |
|
// 4: handshake header size |
|
// 2: ClientHello.client_version |
|
// 32: ClientHello.random |
|
// 1: length byte of ClientHello.session_id |
|
// 2: length bytes of ClientHello.cipher_suites |
|
|
|
int requiredSize = 48 + sessionIdLen + ((cipherSpecLen * 2 ) / 3); |
|
byte[] converted = new byte[requiredSize]; |
|
|
|
/* |
|
* Build the first part of the V3 record header from the V2 one |
|
* that's now buffered up. (Lengths are fixed up later). |
|
*/ |
|
|
|
converted[0] = ContentType.HANDSHAKE.id; |
|
converted[1] = majorVersion; |
|
converted[2] = minorVersion; |
|
// header [3..4] for handshake message length |
|
// required size is 5; |
|
|
|
/* |
|
* Store the generic V3 handshake header: 4 bytes |
|
*/ |
|
converted[5] = 1; |
|
// buf [6..8] for length of ClientHello (int24) |
|
// required size += 4; |
|
|
|
|
|
|
|
*/ |
|
converted[9] = majorVersion; |
|
converted[10] = minorVersion; |
|
|
|
int pointer = 11; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int offset = srcPos + 11 + cipherSpecLen + sessionIdLen; |
|
|
|
if (nonceLen < 32) { |
|
for (int i = 0; i < (32 - nonceLen); i++) { |
|
converted[pointer++] = 0; |
|
} |
|
packet.position(offset); |
|
packet.get(converted, pointer, nonceLen); |
|
|
|
pointer += nonceLen; |
|
} else { |
|
packet.position(offset + nonceLen - 32); |
|
packet.get(converted, pointer, 32); |
|
|
|
pointer += 32; |
|
} |
|
|
|
|
|
|
|
*/ |
|
offset -= sessionIdLen; |
|
converted[pointer++] = (byte)(sessionIdLen & 0xFF); |
|
packet.position(offset); |
|
packet.get(converted, pointer, sessionIdLen); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int j; |
|
|
|
offset -= cipherSpecLen; |
|
packet.position(offset); |
|
|
|
j = pointer + 2; |
|
for (int i = 0; i < cipherSpecLen; i += 3) { |
|
if (packet.get() != 0) { |
|
// Ignore version 2.0 specific cipher suite. Clients |
|
// should also include the version 3.0 equivalent in |
|
// the V2ClientHello message. |
|
packet.get(); |
|
packet.get(); |
|
continue; |
|
} |
|
|
|
converted[j++] = packet.get(); |
|
converted[j++] = packet.get(); |
|
} |
|
|
|
j -= pointer + 2; |
|
converted[pointer++] = (byte)((j >>> 8) & 0xFF); |
|
converted[pointer++] = (byte)(j & 0xFF); |
|
pointer += j; |
|
|
|
|
|
|
|
*/ |
|
converted[pointer++] = 1; |
|
converted[pointer++] = 0; |
|
|
|
/* |
|
* Fill in lengths of the messages we synthesized (nested: |
|
* V3 handshake message within V3 record). |
|
*/ |
|
// Note: need not to set the header actually. |
|
int fragLen = pointer - 5; |
|
converted[3] = (byte)((fragLen >>> 8) & 0xFF); |
|
converted[4] = (byte)(fragLen & 0xFF); |
|
|
|
/* |
|
* Handshake.length, length of ClientHello message |
|
*/ |
|
fragLen = pointer - 9; |
|
converted[6] = (byte)((fragLen >>> 16) & 0xFF); |
|
converted[7] = (byte)((fragLen >>> 8) & 0xFF); |
|
converted[8] = (byte)(fragLen & 0xFF); |
|
|
|
|
|
packet.position(srcPos + recordLen); |
|
|
|
// Need no header bytes. |
|
return ByteBuffer.wrap(converted, 5, pointer - 5); |
|
} |
|
|
|
|
|
static ByteBuffer extract( |
|
ByteBuffer[] buffers, int offset, int length, int headerSize) { |
|
|
|
boolean hasFullHeader = false; |
|
int contentLen = -1; |
|
for (int i = offset, j = 0; |
|
i < (offset + length) && j < headerSize; i++) { |
|
int remains = buffers[i].remaining(); |
|
int pos = buffers[i].position(); |
|
for (int k = 0; k < remains && j < headerSize; j++, k++) { |
|
byte b = buffers[i].get(pos + k); |
|
if (j == (headerSize - 2)) { |
|
contentLen = ((b & 0xFF) << 8); |
|
} else if (j == (headerSize -1)) { |
|
contentLen |= (b & 0xFF); |
|
hasFullHeader = true; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (!hasFullHeader) { |
|
throw new BufferUnderflowException(); |
|
} |
|
|
|
int packetLen = headerSize + contentLen; |
|
int remains = 0; |
|
for (int i = offset; i < offset + length; i++) { |
|
remains += buffers[i].remaining(); |
|
if (remains >= packetLen) { |
|
break; |
|
} |
|
} |
|
|
|
if (remains < packetLen) { |
|
throw new BufferUnderflowException(); |
|
} |
|
|
|
byte[] packet = new byte[packetLen]; |
|
int packetOffset = 0; |
|
int packetSpaces = packetLen; |
|
for (int i = offset; i < offset + length; i++) { |
|
if (buffers[i].hasRemaining()) { |
|
int len = Math.min(packetSpaces, buffers[i].remaining()); |
|
buffers[i].get(packet, packetOffset, len); |
|
packetOffset += len; |
|
packetSpaces -= len; |
|
} |
|
|
|
if (packetSpaces <= 0) { |
|
break; |
|
} |
|
} |
|
|
|
return ByteBuffer.wrap(packet); |
|
} |
|
} |