|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.ByteArrayOutputStream; |
|
import java.io.Closeable; |
|
import java.io.IOException; |
|
import java.io.OutputStream; |
|
import java.nio.ByteBuffer; |
|
import sun.security.ssl.SSLCipher.SSLWriteCipher; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
abstract class OutputRecord |
|
extends ByteArrayOutputStream implements Record, Closeable { |
|
SSLWriteCipher writeCipher; |
|
|
|
TransportContext tc; |
|
|
|
final HandshakeHash handshakeHash; |
|
boolean firstMessage; |
|
|
|
|
|
ProtocolVersion protocolVersion; |
|
|
|
// version for the ClientHello message. Only relevant if this is a |
|
// client handshake record. If set to ProtocolVersion.SSL20Hello, |
|
|
|
ProtocolVersion helloVersion; |
|
|
|
|
|
boolean isFirstAppOutputRecord = true; |
|
|
|
|
|
int packetSize; |
|
|
|
|
|
int fragmentSize; |
|
|
|
|
|
volatile boolean isClosed; |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final int[] V3toV2CipherMap1 = |
|
{-1, -1, -1, 0x02, 0x01, -1, 0x04, 0x05, -1, 0x06, 0x07}; |
|
private static final int[] V3toV2CipherMap3 = |
|
{-1, -1, -1, 0x80, 0x80, -1, 0x80, 0x80, -1, 0x40, 0xC0}; |
|
private static final byte[] HANDSHAKE_MESSAGE_KEY_UPDATE = |
|
{SSLHandshake.KEY_UPDATE.id, 0x00, 0x00, 0x01, 0x00}; |
|
|
|
OutputRecord(HandshakeHash handshakeHash, SSLWriteCipher writeCipher) { |
|
this.writeCipher = writeCipher; |
|
this.firstMessage = true; |
|
this.fragmentSize = Record.maxDataSize; |
|
|
|
this.handshakeHash = handshakeHash; |
|
|
|
// Please set packetSize and protocolVersion in the implementation. |
|
} |
|
|
|
synchronized void setVersion(ProtocolVersion protocolVersion) { |
|
this.protocolVersion = protocolVersion; |
|
} |
|
|
|
|
|
|
|
*/ |
|
synchronized void setHelloVersion(ProtocolVersion helloVersion) { |
|
this.helloVersion = helloVersion; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
boolean isEmpty() { |
|
return false; |
|
} |
|
|
|
synchronized boolean seqNumIsHuge() { |
|
return (writeCipher.authenticator != null) && |
|
writeCipher.authenticator.seqNumIsHuge(); |
|
} |
|
|
|
|
|
abstract void encodeAlert(byte level, byte description) throws IOException; |
|
|
|
|
|
abstract void encodeHandshake(byte[] buffer, |
|
int offset, int length) throws IOException; |
|
|
|
|
|
abstract void encodeChangeCipherSpec() throws IOException; |
|
|
|
|
|
Ciphertext encode( |
|
ByteBuffer[] srcs, int srcsOffset, int srcsLength, |
|
ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { |
|
|
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
|
|
void encodeV2NoCipher() throws IOException { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
|
|
void deliver( |
|
byte[] source, int offset, int length) throws IOException { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
|
|
void setDeliverStream(OutputStream outputStream) { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
|
|
synchronized void changeWriteCiphers(SSLWriteCipher writeCipher, |
|
boolean useChangeCipherSpec) throws IOException { |
|
if (isClosed()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("outbound has closed, ignore outbound " + |
|
"change_cipher_spec message"); |
|
} |
|
return; |
|
} |
|
|
|
if (useChangeCipherSpec) { |
|
encodeChangeCipherSpec(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
writeCipher.dispose(); |
|
|
|
this.writeCipher = writeCipher; |
|
this.isFirstAppOutputRecord = true; |
|
} |
|
|
|
|
|
synchronized void changeWriteCiphers(SSLWriteCipher writeCipher, |
|
byte keyUpdateRequest) throws IOException { |
|
if (isClosed()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("outbound has closed, ignore outbound " + |
|
"key_update handshake message"); |
|
} |
|
return; |
|
} |
|
|
|
|
|
byte[] hm = HANDSHAKE_MESSAGE_KEY_UPDATE.clone(); |
|
hm[hm.length - 1] = keyUpdateRequest; |
|
encodeHandshake(hm, 0, hm.length); |
|
flush(); |
|
|
|
|
|
writeCipher.dispose(); |
|
|
|
this.writeCipher = writeCipher; |
|
this.isFirstAppOutputRecord = true; |
|
} |
|
|
|
synchronized void changePacketSize(int packetSize) { |
|
this.packetSize = packetSize; |
|
} |
|
|
|
synchronized void changeFragmentSize(int fragmentSize) { |
|
this.fragmentSize = fragmentSize; |
|
} |
|
|
|
synchronized int getMaxPacketSize() { |
|
return packetSize; |
|
} |
|
|
|
|
|
void initHandshaker() { |
|
// blank |
|
} |
|
|
|
|
|
void finishHandshake() { |
|
// blank |
|
} |
|
|
|
|
|
void launchRetransmission() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public synchronized void close() throws IOException { |
|
if (isClosed) { |
|
return; |
|
} |
|
|
|
isClosed = true; |
|
writeCipher.dispose(); |
|
} |
|
|
|
boolean isClosed() { |
|
return isClosed; |
|
} |
|
|
|
// |
|
// shared helpers |
|
// |
|
|
|
// Encrypt a fragment and wrap up a record. |
|
// |
|
// To be consistent with the spec of SSLEngine.wrap() methods, the |
|
// destination ByteBuffer's position is updated to reflect the amount |
|
|
|
static long encrypt( |
|
SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, |
|
int headerOffset, int dstLim, int headerSize, |
|
ProtocolVersion protocolVersion) { |
|
boolean isDTLS = protocolVersion.isDTLS; |
|
if (isDTLS) { |
|
if (protocolVersion.useTLS13PlusSpec()) { |
|
return d13Encrypt(encCipher, |
|
contentType, destination, headerOffset, |
|
dstLim, headerSize, protocolVersion); |
|
} else { |
|
return d10Encrypt(encCipher, |
|
contentType, destination, headerOffset, |
|
dstLim, headerSize, protocolVersion); |
|
} |
|
} else { |
|
if (protocolVersion.useTLS13PlusSpec()) { |
|
return t13Encrypt(encCipher, |
|
contentType, destination, headerOffset, |
|
dstLim, headerSize, protocolVersion); |
|
} else { |
|
return t10Encrypt(encCipher, |
|
contentType, destination, headerOffset, |
|
dstLim, headerSize, protocolVersion); |
|
} |
|
} |
|
} |
|
|
|
private static long d13Encrypt( |
|
SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, |
|
int headerOffset, int dstLim, int headerSize, |
|
ProtocolVersion protocolVersion) { |
|
throw new UnsupportedOperationException("Not supported yet."); |
|
} |
|
|
|
private static long d10Encrypt( |
|
SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, |
|
int headerOffset, int dstLim, int headerSize, |
|
ProtocolVersion protocolVersion) { |
|
byte[] sequenceNumber = encCipher.authenticator.sequenceNumber(); |
|
encCipher.encrypt(contentType, destination); |
|
|
|
|
|
int fragLen = destination.limit() - headerOffset - headerSize; |
|
|
|
destination.put(headerOffset, contentType); |
|
destination.put(headerOffset + 1, protocolVersion.major); |
|
destination.put(headerOffset + 2, protocolVersion.minor); |
|
|
|
|
|
destination.put(headerOffset + 3, sequenceNumber[0]); |
|
destination.put(headerOffset + 4, sequenceNumber[1]); |
|
destination.put(headerOffset + 5, sequenceNumber[2]); |
|
destination.put(headerOffset + 6, sequenceNumber[3]); |
|
destination.put(headerOffset + 7, sequenceNumber[4]); |
|
destination.put(headerOffset + 8, sequenceNumber[5]); |
|
destination.put(headerOffset + 9, sequenceNumber[6]); |
|
destination.put(headerOffset + 10, sequenceNumber[7]); |
|
|
|
|
|
destination.put(headerOffset + 11, (byte)(fragLen >> 8)); |
|
destination.put(headerOffset + 12, (byte)fragLen); |
|
|
|
|
|
destination.position(destination.limit()); |
|
|
|
return Authenticator.toLong(sequenceNumber); |
|
} |
|
|
|
private static long t13Encrypt( |
|
SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, |
|
int headerOffset, int dstLim, int headerSize, |
|
ProtocolVersion protocolVersion) { |
|
if (!encCipher.isNullCipher()) { |
|
|
|
int endOfPt = destination.limit(); |
|
destination.limit(endOfPt + 1); |
|
destination.put(endOfPt, contentType); |
|
} |
|
|
|
|
|
ProtocolVersion pv = protocolVersion; |
|
if (!encCipher.isNullCipher()) { |
|
pv = ProtocolVersion.TLS12; |
|
contentType = ContentType.APPLICATION_DATA.id; |
|
} else if (protocolVersion.useTLS13PlusSpec()) { |
|
pv = ProtocolVersion.TLS12; |
|
} |
|
|
|
byte[] sequenceNumber = encCipher.authenticator.sequenceNumber(); |
|
encCipher.encrypt(contentType, destination); |
|
|
|
|
|
int fragLen = destination.limit() - headerOffset - headerSize; |
|
destination.put(headerOffset, contentType); |
|
destination.put(headerOffset + 1, pv.major); |
|
destination.put(headerOffset + 2, pv.minor); |
|
|
|
|
|
destination.put(headerOffset + 3, (byte)(fragLen >> 8)); |
|
destination.put(headerOffset + 4, (byte)fragLen); |
|
|
|
|
|
destination.position(destination.limit()); |
|
|
|
return Authenticator.toLong(sequenceNumber); |
|
} |
|
|
|
private static long t10Encrypt( |
|
SSLWriteCipher encCipher, byte contentType, ByteBuffer destination, |
|
int headerOffset, int dstLim, int headerSize, |
|
ProtocolVersion protocolVersion) { |
|
byte[] sequenceNumber = encCipher.authenticator.sequenceNumber(); |
|
encCipher.encrypt(contentType, destination); |
|
|
|
|
|
int fragLen = destination.limit() - headerOffset - headerSize; |
|
|
|
destination.put(headerOffset, contentType); |
|
destination.put(headerOffset + 1, protocolVersion.major); |
|
destination.put(headerOffset + 2, protocolVersion.minor); |
|
|
|
|
|
destination.put(headerOffset + 3, (byte)(fragLen >> 8)); |
|
destination.put(headerOffset + 4, (byte)fragLen); |
|
|
|
|
|
destination.position(destination.limit()); |
|
|
|
return Authenticator.toLong(sequenceNumber); |
|
} |
|
|
|
// Encrypt a fragment and wrap up a record. |
|
// |
|
// Uses the internal expandable buf variable and the current |
|
|
|
long encrypt( |
|
SSLWriteCipher encCipher, byte contentType, int headerSize) { |
|
if (protocolVersion.useTLS13PlusSpec()) { |
|
return t13Encrypt(encCipher, contentType, headerSize); |
|
} else { |
|
return t10Encrypt(encCipher, contentType, headerSize); |
|
} |
|
} |
|
|
|
private static final class T13PaddingHolder { |
|
private static final byte[] zeros = new byte[16]; |
|
} |
|
|
|
private long t13Encrypt( |
|
SSLWriteCipher encCipher, byte contentType, int headerSize) { |
|
if (!encCipher.isNullCipher()) { |
|
|
|
write(contentType); |
|
write(T13PaddingHolder.zeros, 0, T13PaddingHolder.zeros.length); |
|
} |
|
|
|
byte[] sequenceNumber = encCipher.authenticator.sequenceNumber(); |
|
int position = headerSize; |
|
int contentLen = count - position; |
|
|
|
|
|
int requiredPacketSize = |
|
encCipher.calculatePacketSize(contentLen, headerSize); |
|
if (requiredPacketSize > buf.length) { |
|
byte[] newBuf = new byte[requiredPacketSize]; |
|
System.arraycopy(buf, 0, newBuf, 0, count); |
|
buf = newBuf; |
|
} |
|
|
|
|
|
ProtocolVersion pv = protocolVersion; |
|
if (!encCipher.isNullCipher()) { |
|
pv = ProtocolVersion.TLS12; |
|
contentType = ContentType.APPLICATION_DATA.id; |
|
} else { |
|
pv = ProtocolVersion.TLS12; |
|
} |
|
|
|
ByteBuffer destination = ByteBuffer.wrap(buf, position, contentLen); |
|
count = headerSize + encCipher.encrypt(contentType, destination); |
|
|
|
|
|
int fragLen = count - headerSize; |
|
|
|
buf[0] = contentType; |
|
buf[1] = pv.major; |
|
buf[2] = pv.minor; |
|
buf[3] = (byte)((fragLen >> 8) & 0xFF); |
|
buf[4] = (byte)(fragLen & 0xFF); |
|
|
|
return Authenticator.toLong(sequenceNumber); |
|
} |
|
|
|
private long t10Encrypt( |
|
SSLWriteCipher encCipher, byte contentType, int headerSize) { |
|
byte[] sequenceNumber = encCipher.authenticator.sequenceNumber(); |
|
int position = headerSize + writeCipher.getExplicitNonceSize(); |
|
int contentLen = count - position; |
|
|
|
|
|
int requiredPacketSize = |
|
encCipher.calculatePacketSize(contentLen, headerSize); |
|
if (requiredPacketSize > buf.length) { |
|
byte[] newBuf = new byte[requiredPacketSize]; |
|
System.arraycopy(buf, 0, newBuf, 0, count); |
|
buf = newBuf; |
|
} |
|
ByteBuffer destination = ByteBuffer.wrap(buf, position, contentLen); |
|
count = headerSize + encCipher.encrypt(contentType, destination); |
|
|
|
|
|
int fragLen = count - headerSize; |
|
buf[0] = contentType; |
|
buf[1] = protocolVersion.major; |
|
buf[2] = protocolVersion.minor; |
|
buf[3] = (byte)((fragLen >> 8) & 0xFF); |
|
buf[4] = (byte)(fragLen & 0xFF); |
|
|
|
return Authenticator.toLong(sequenceNumber); |
|
} |
|
|
|
static ByteBuffer encodeV2ClientHello( |
|
byte[] fragment, int offset, int length) throws IOException { |
|
int v3SessIdLenOffset = offset + 34; |
|
// 32: random |
|
|
|
int v3SessIdLen = fragment[v3SessIdLenOffset]; |
|
int v3CSLenOffset = v3SessIdLenOffset + 1 + v3SessIdLen; |
|
int v3CSLen = ((fragment[v3CSLenOffset] & 0xff) << 8) + |
|
(fragment[v3CSLenOffset + 1] & 0xff); |
|
int cipherSpecs = v3CSLen / 2; |
|
|
|
// Estimate the max V2ClientHello message length |
|
// |
|
// 11: header size |
|
// (cipherSpecs * 6): cipher_specs |
|
// 6: one cipher suite may need 6 bytes, see V3toV2CipherSuite. |
|
// 3: placeholder for the TLS_EMPTY_RENEGOTIATION_INFO_SCSV |
|
// signaling cipher suite |
|
|
|
int v2MaxMsgLen = 11 + (cipherSpecs * 6) + 3 + 32; |
|
|
|
|
|
byte[] dstBytes = new byte[v2MaxMsgLen]; |
|
ByteBuffer dstBuf = ByteBuffer.wrap(dstBytes); |
|
|
|
/* |
|
* Copy over the cipher specs. We don't care about actually |
|
* translating them for use with an actual V2 server since |
|
* we only talk V3. Therefore, just copy over the V3 cipher |
|
* spec values with a leading 0. |
|
*/ |
|
int v3CSOffset = v3CSLenOffset + 2; |
|
int v2CSLen = 0; |
|
|
|
dstBuf.position(11); |
|
boolean containsRenegoInfoSCSV = false; |
|
for (int i = 0; i < cipherSpecs; i++) { |
|
byte byte1, byte2; |
|
|
|
byte1 = fragment[v3CSOffset++]; |
|
byte2 = fragment[v3CSOffset++]; |
|
v2CSLen += V3toV2CipherSuite(dstBuf, byte1, byte2); |
|
if (!containsRenegoInfoSCSV && |
|
byte1 == (byte)0x00 && byte2 == (byte)0xFF) { |
|
containsRenegoInfoSCSV = true; |
|
} |
|
} |
|
|
|
if (!containsRenegoInfoSCSV) { |
|
v2CSLen += V3toV2CipherSuite(dstBuf, (byte)0x00, (byte)0xFF); |
|
} |
|
|
|
|
|
|
|
*/ |
|
dstBuf.put(fragment, (offset + 2), 32); |
|
|
|
/* |
|
* Build the first part of the V3 record header from the V2 one |
|
* that's now buffered up. (Lengths are fixed up later). |
|
*/ |
|
int msgLen = dstBuf.position() - 2; |
|
dstBuf.position(0); |
|
dstBuf.put((byte)(0x80 | ((msgLen >>> 8) & 0xFF))); |
|
dstBuf.put((byte)(msgLen & 0xFF)); |
|
dstBuf.put(SSLHandshake.CLIENT_HELLO.id); |
|
dstBuf.put(fragment[offset]); |
|
dstBuf.put(fragment[offset + 1]); |
|
dstBuf.put((byte)(v2CSLen >>> 8)); |
|
dstBuf.put((byte)(v2CSLen & 0xFF)); |
|
dstBuf.put((byte)0x00); |
|
dstBuf.put((byte)0x00); |
|
dstBuf.put((byte)0x00); |
|
dstBuf.put((byte)32); |
|
|
|
dstBuf.position(0); |
|
dstBuf.limit(msgLen + 2); |
|
|
|
return dstBuf; |
|
} |
|
|
|
private static int V3toV2CipherSuite(ByteBuffer dstBuf, |
|
byte byte1, byte byte2) { |
|
dstBuf.put((byte)0); |
|
dstBuf.put(byte1); |
|
dstBuf.put(byte2); |
|
|
|
if (((byte2 & 0xff) > 0xA) || (V3toV2CipherMap1[byte2] == -1)) { |
|
return 3; |
|
} |
|
|
|
dstBuf.put((byte)V3toV2CipherMap1[byte2]); |
|
dstBuf.put((byte)0); |
|
dstBuf.put((byte)V3toV2CipherMap3[byte2]); |
|
|
|
return 6; |
|
} |
|
} |