|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.ByteArrayInputStream; |
|
import java.io.IOException; |
|
import java.io.OutputStream; |
|
import java.net.SocketException; |
|
import java.nio.ByteBuffer; |
|
import javax.net.ssl.SSLHandshakeException; |
|
|
|
|
|
|
|
*/ |
|
final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { |
|
private OutputStream deliverStream = null; |
|
|
|
SSLSocketOutputRecord(HandshakeHash handshakeHash) { |
|
this(handshakeHash, null); |
|
} |
|
|
|
SSLSocketOutputRecord(HandshakeHash handshakeHash, |
|
TransportContext tc) { |
|
super(handshakeHash, SSLCipher.SSLWriteCipher.nullTlsWriteCipher()); |
|
this.tc = tc; |
|
this.packetSize = SSLRecord.maxRecordSize; |
|
this.protocolVersion = ProtocolVersion.NONE; |
|
} |
|
|
|
@Override |
|
void encodeAlert(byte level, byte description) throws IOException { |
|
recordLock.lock(); |
|
try { |
|
if (isClosed()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("outbound has closed, ignore outbound " + |
|
"alert message: " + Alert.nameOf(description)); |
|
} |
|
return; |
|
} |
|
|
|
|
|
count = headerSize + writeCipher.getExplicitNonceSize(); |
|
|
|
write(level); |
|
write(description); |
|
if (SSLLogger.isOn && SSLLogger.isOn("record")) { |
|
SSLLogger.fine("WRITE: " + protocolVersion.name + |
|
" " + ContentType.ALERT.name + |
|
"(" + Alert.nameOf(description) + ")" + |
|
", length = " + (count - headerSize)); |
|
} |
|
|
|
|
|
encrypt(writeCipher, ContentType.ALERT.id, headerSize); |
|
|
|
// deliver this message |
|
deliverStream.write(buf, 0, count); |
|
deliverStream.flush(); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("packet")) { |
|
SSLLogger.fine("Raw write", |
|
(new ByteArrayInputStream(buf, 0, count))); |
|
} |
|
|
|
|
|
count = 0; |
|
} finally { |
|
recordLock.unlock(); |
|
} |
|
} |
|
|
|
@Override |
|
void encodeHandshake(byte[] source, |
|
int offset, int length) throws IOException { |
|
recordLock.lock(); |
|
try { |
|
if (isClosed()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("outbound has closed, ignore outbound " + |
|
"handshake message", |
|
ByteBuffer.wrap(source, offset, length)); |
|
} |
|
return; |
|
} |
|
|
|
if (firstMessage) { |
|
firstMessage = false; |
|
|
|
if ((helloVersion == ProtocolVersion.SSL20Hello) && |
|
(source[offset] == SSLHandshake.CLIENT_HELLO.id) && |
|
|
|
(source[offset + 4 + 2 + 32] == 0)) { |
|
// V3 session ID is empty |
|
// 4: handshake header size |
|
// 2: client_version in ClientHello |
|
// 32: random in ClientHello |
|
|
|
ByteBuffer v2ClientHello = encodeV2ClientHello( |
|
source, (offset + 4), (length - 4)); |
|
|
|
|
|
byte[] record = v2ClientHello.array(); |
|
int limit = v2ClientHello.limit(); |
|
handshakeHash.deliver(record, 2, (limit - 2)); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("record")) { |
|
SSLLogger.fine( |
|
"WRITE: SSLv2 ClientHello message" + |
|
", length = " + limit); |
|
} |
|
|
|
// deliver this message |
|
// |
|
// Version 2 ClientHello message should be plaintext. |
|
// |
|
|
|
deliverStream.write(record, 0, limit); |
|
deliverStream.flush(); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("packet")) { |
|
SSLLogger.fine("Raw write", |
|
(new ByteArrayInputStream(record, 0, limit))); |
|
} |
|
|
|
return; |
|
} |
|
} |
|
|
|
byte handshakeType = source[0]; |
|
if (handshakeHash.isHashable(handshakeType)) { |
|
handshakeHash.deliver(source, offset, length); |
|
} |
|
|
|
int fragLimit = getFragLimit(); |
|
int position = headerSize + writeCipher.getExplicitNonceSize(); |
|
if (count == 0) { |
|
count = position; |
|
} |
|
|
|
if ((count - position) < (fragLimit - length)) { |
|
write(source, offset, length); |
|
return; |
|
} |
|
|
|
for (int limit = (offset + length); offset < limit;) { |
|
|
|
int remains = (limit - offset) + (count - position); |
|
int fragLen = Math.min(fragLimit, remains); |
|
|
|
|
|
write(source, offset, fragLen); |
|
if (remains < fragLimit) { |
|
return; |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("record")) { |
|
SSLLogger.fine( |
|
"WRITE: " + protocolVersion.name + |
|
" " + ContentType.HANDSHAKE.name + |
|
", length = " + (count - headerSize)); |
|
} |
|
|
|
|
|
encrypt(writeCipher, ContentType.HANDSHAKE.id, headerSize); |
|
|
|
// deliver this message |
|
deliverStream.write(buf, 0, count); |
|
deliverStream.flush(); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("packet")) { |
|
SSLLogger.fine("Raw write", |
|
(new ByteArrayInputStream(buf, 0, count))); |
|
} |
|
|
|
|
|
offset += fragLen; |
|
|
|
|
|
count = position; |
|
} |
|
} finally { |
|
recordLock.unlock(); |
|
} |
|
} |
|
|
|
@Override |
|
void encodeChangeCipherSpec() throws IOException { |
|
recordLock.lock(); |
|
try { |
|
if (isClosed()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("outbound has closed, ignore outbound " + |
|
"change_cipher_spec message"); |
|
} |
|
return; |
|
} |
|
|
|
|
|
count = headerSize + writeCipher.getExplicitNonceSize(); |
|
|
|
write((byte)1); |
|
|
|
|
|
encrypt(writeCipher, ContentType.CHANGE_CIPHER_SPEC.id, headerSize); |
|
|
|
// deliver this message |
|
deliverStream.write(buf, 0, count); |
|
// deliverStream.flush(); // flush in Finished |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("packet")) { |
|
SSLLogger.fine("Raw write", |
|
(new ByteArrayInputStream(buf, 0, count))); |
|
} |
|
|
|
|
|
count = 0; |
|
} finally { |
|
recordLock.unlock(); |
|
} |
|
} |
|
|
|
@Override |
|
public void flush() throws IOException { |
|
recordLock.lock(); |
|
try { |
|
int position = headerSize + writeCipher.getExplicitNonceSize(); |
|
if (count <= position) { |
|
return; |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("record")) { |
|
SSLLogger.fine( |
|
"WRITE: " + protocolVersion.name + |
|
" " + ContentType.HANDSHAKE.name + |
|
", length = " + (count - headerSize)); |
|
} |
|
|
|
|
|
encrypt(writeCipher, ContentType.HANDSHAKE.id, headerSize); |
|
|
|
// deliver this message |
|
deliverStream.write(buf, 0, count); |
|
deliverStream.flush(); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("packet")) { |
|
SSLLogger.fine("Raw write", |
|
(new ByteArrayInputStream(buf, 0, count))); |
|
} |
|
|
|
// reset the internal buffer |
|
count = 0; |
|
} finally { |
|
recordLock.unlock(); |
|
} |
|
} |
|
|
|
@Override |
|
void deliver(byte[] source, int offset, int length) throws IOException { |
|
recordLock.lock(); |
|
try { |
|
if (isClosed()) { |
|
throw new SocketException( |
|
"Connection or outbound has been closed"); |
|
} |
|
|
|
if (writeCipher.authenticator.seqNumOverflow()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.fine( |
|
"sequence number extremely close to overflow " + |
|
"(2^64-1 packets). Closing connection."); |
|
} |
|
|
|
throw new SSLHandshakeException("sequence number overflow"); |
|
} |
|
|
|
boolean isFirstRecordOfThePayload = true; |
|
for (int limit = (offset + length); offset < limit;) { |
|
int fragLen; |
|
if (packetSize > 0) { |
|
fragLen = Math.min(maxRecordSize, packetSize); |
|
fragLen = writeCipher.calculateFragmentSize( |
|
fragLen, headerSize); |
|
|
|
fragLen = Math.min(fragLen, Record.maxDataSize); |
|
} else { |
|
fragLen = Record.maxDataSize; |
|
} |
|
|
|
|
|
fragLen = calculateFragmentSize(fragLen); |
|
|
|
if (isFirstRecordOfThePayload && needToSplitPayload()) { |
|
fragLen = 1; |
|
isFirstRecordOfThePayload = false; |
|
} else { |
|
fragLen = Math.min(fragLen, (limit - offset)); |
|
} |
|
|
|
|
|
int position = headerSize + writeCipher.getExplicitNonceSize(); |
|
count = position; |
|
write(source, offset, fragLen); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("record")) { |
|
SSLLogger.fine( |
|
"WRITE: " + protocolVersion.name + |
|
" " + ContentType.APPLICATION_DATA.name + |
|
", length = " + (count - position)); |
|
} |
|
|
|
|
|
encrypt(writeCipher, |
|
ContentType.APPLICATION_DATA.id, headerSize); |
|
|
|
// deliver this message |
|
deliverStream.write(buf, 0, count); |
|
deliverStream.flush(); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("packet")) { |
|
SSLLogger.fine("Raw write", |
|
(new ByteArrayInputStream(buf, 0, count))); |
|
} |
|
|
|
|
|
count = 0; |
|
|
|
if (isFirstAppOutputRecord) { |
|
isFirstAppOutputRecord = false; |
|
} |
|
|
|
offset += fragLen; |
|
} |
|
} finally { |
|
recordLock.unlock(); |
|
} |
|
} |
|
|
|
@Override |
|
void setDeliverStream(OutputStream outputStream) { |
|
recordLock.lock(); |
|
try { |
|
this.deliverStream = outputStream; |
|
} finally { |
|
recordLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private boolean needToSplitPayload() { |
|
return (!protocolVersion.useTLS11PlusSpec()) && |
|
writeCipher.isCBCMode() && !isFirstAppOutputRecord && |
|
Record.enableCBCProtection; |
|
} |
|
|
|
private int getFragLimit() { |
|
int fragLimit; |
|
if (packetSize > 0) { |
|
fragLimit = Math.min(maxRecordSize, packetSize); |
|
fragLimit = |
|
writeCipher.calculateFragmentSize(fragLimit, headerSize); |
|
|
|
fragLimit = Math.min(fragLimit, Record.maxDataSize); |
|
} else { |
|
fragLimit = Record.maxDataSize; |
|
} |
|
|
|
|
|
fragLimit = calculateFragmentSize(fragLimit); |
|
|
|
return fragLimit; |
|
} |
|
} |