|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.IOException; |
|
import java.nio.ByteBuffer; |
|
import java.security.GeneralSecurityException; |
|
import java.util.Collections; |
|
import java.util.HashMap; |
|
import java.util.Iterator; |
|
import java.util.LinkedList; |
|
import java.util.List; |
|
import java.util.Set; |
|
import java.util.TreeSet; |
|
import javax.crypto.BadPaddingException; |
|
import javax.net.ssl.SSLException; |
|
import sun.security.ssl.SSLCipher.SSLReadCipher; |
|
|
|
|
|
|
|
*/ |
|
final class DTLSInputRecord extends InputRecord implements DTLSRecord { |
|
private DTLSReassembler reassembler = null; |
|
private int readEpoch; |
|
|
|
DTLSInputRecord(HandshakeHash handshakeHash) { |
|
super(handshakeHash, SSLReadCipher.nullDTlsReadCipher()); |
|
this.readEpoch = 0; |
|
} |
|
|
|
@Override |
|
void changeReadCiphers(SSLReadCipher readCipher) { |
|
this.readCipher = readCipher; |
|
this.readEpoch++; |
|
} |
|
|
|
@Override |
|
public synchronized void close() throws IOException { |
|
if (!isClosed) { |
|
super.close(); |
|
} |
|
} |
|
|
|
@Override |
|
boolean isEmpty() { |
|
return ((reassembler == null) || reassembler.isEmpty()); |
|
} |
|
|
|
@Override |
|
int estimateFragmentSize(int packetSize) { |
|
if (packetSize > 0) { |
|
return readCipher.estimateFragmentSize(packetSize, headerSize); |
|
} else { |
|
return Record.maxDataSize; |
|
} |
|
} |
|
|
|
@Override |
|
void expectingFinishFlight() { |
|
if (reassembler != null) { |
|
reassembler.expectingFinishFlight(); |
|
} |
|
} |
|
|
|
@Override |
|
void finishHandshake() { |
|
reassembler = null; |
|
} |
|
|
|
@Override |
|
Plaintext acquirePlaintext() { |
|
if (reassembler != null) { |
|
return reassembler.acquirePlaintext(); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
@Override |
|
Plaintext[] decode(ByteBuffer[] srcs, int srcsOffset, |
|
int srcsLength) throws IOException, BadPaddingException { |
|
if (srcs == null || srcs.length == 0 || srcsLength == 0) { |
|
Plaintext pt = acquirePlaintext(); |
|
return pt == null ? new Plaintext[0] : new Plaintext[] { pt }; |
|
} else if (srcsLength == 1) { |
|
return decode(srcs[srcsOffset]); |
|
} else { |
|
ByteBuffer packet = extract(srcs, |
|
srcsOffset, srcsLength, DTLSRecord.headerSize); |
|
return decode(packet); |
|
} |
|
} |
|
|
|
Plaintext[] decode(ByteBuffer packet) { |
|
if (isClosed) { |
|
return null; |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("packet")) { |
|
SSLLogger.fine("Raw read", packet); |
|
} |
|
|
|
|
|
int srcPos = packet.position(); |
|
int srcLim = packet.limit(); |
|
|
|
byte contentType = packet.get(); |
|
byte majorVersion = packet.get(); |
|
byte minorVersion = packet.get(); |
|
byte[] recordEnS = new byte[8]; |
|
packet.get(recordEnS); |
|
int recordEpoch = ((recordEnS[0] & 0xFF) << 8) | |
|
(recordEnS[1] & 0xFF); |
|
long recordSeq = ((recordEnS[2] & 0xFFL) << 40) | |
|
((recordEnS[3] & 0xFFL) << 32) | |
|
((recordEnS[4] & 0xFFL) << 24) | |
|
((recordEnS[5] & 0xFFL) << 16) | |
|
((recordEnS[6] & 0xFFL) << 8) | |
|
(recordEnS[7] & 0xFFL); |
|
|
|
int contentLen = ((packet.get() & 0xFF) << 8) | |
|
(packet.get() & 0xFF); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("record")) { |
|
SSLLogger.fine("READ: " + |
|
ProtocolVersion.nameOf(majorVersion, minorVersion) + |
|
" " + ContentType.nameOf(contentType) + ", length = " + |
|
contentLen); |
|
} |
|
|
|
int recLim = Math.addExact(srcPos, DTLSRecord.headerSize + contentLen); |
|
|
|
if (this.readEpoch > recordEpoch) { |
|
|
|
packet.position(recLim); |
|
if (SSLLogger.isOn && SSLLogger.isOn("record")) { |
|
SSLLogger.fine("READ: discard this old record", recordEnS); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
if (this.readEpoch < recordEpoch) { |
|
// Discard the record younger than the current epcoh if: |
|
// 1. it is not a handshake message, or |
|
|
|
if ((contentType != ContentType.HANDSHAKE.id && |
|
contentType != ContentType.CHANGE_CIPHER_SPEC.id) || |
|
(reassembler == null && |
|
contentType != ContentType.HANDSHAKE.id) || |
|
(this.readEpoch < (recordEpoch - 1))) { |
|
|
|
packet.position(recLim); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("Premature record (epoch), discard it."); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
|
|
// Not ready to decrypt this record, may be an encrypted Finished |
|
|
|
byte[] fragment = new byte[contentLen]; |
|
packet.get(fragment); |
|
RecordFragment buffered = new RecordFragment(fragment, contentType, |
|
majorVersion, minorVersion, |
|
recordEnS, recordEpoch, recordSeq, true); |
|
|
|
if (reassembler == null) { |
|
reassembler = new DTLSReassembler(recordEpoch); |
|
} |
|
reassembler.queueUpFragment(buffered); |
|
|
|
|
|
packet.position(recLim); |
|
|
|
Plaintext pt = reassembler.acquirePlaintext(); |
|
return pt == null ? null : new Plaintext[] { pt }; |
|
} |
|
|
|
// |
|
// Now, the message is of this epoch. |
|
// |
|
|
|
packet.limit(recLim); |
|
packet.position(srcPos + DTLSRecord.headerSize); |
|
|
|
ByteBuffer plaintextFragment; |
|
try { |
|
Plaintext plaintext = |
|
readCipher.decrypt(contentType, packet, recordEnS); |
|
plaintextFragment = plaintext.fragment; |
|
contentType = plaintext.contentType; |
|
} catch (GeneralSecurityException gse) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.fine("Discard invalid record: " + gse); |
|
} |
|
|
|
|
|
return null; |
|
} finally { |
|
|
|
packet.limit(srcLim); |
|
packet.position(recLim); |
|
} |
|
|
|
if (contentType != ContentType.CHANGE_CIPHER_SPEC.id && |
|
contentType != ContentType.HANDSHAKE.id) { // app data or alert |
|
// no retransmission |
|
|
|
if ((reassembler != null) && |
|
(reassembler.handshakeEpoch < recordEpoch)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("Cleanup the handshake reassembler"); |
|
} |
|
|
|
reassembler = null; |
|
} |
|
|
|
return new Plaintext[] { |
|
new Plaintext(contentType, majorVersion, minorVersion, |
|
recordEpoch, Authenticator.toLong(recordEnS), |
|
plaintextFragment)}; |
|
} |
|
|
|
if (contentType == ContentType.CHANGE_CIPHER_SPEC.id) { |
|
if (reassembler == null) { |
|
reassembler = new DTLSReassembler(recordEpoch); |
|
} |
|
|
|
reassembler.queueUpChangeCipherSpec( |
|
new RecordFragment(plaintextFragment, contentType, |
|
majorVersion, minorVersion, |
|
recordEnS, recordEpoch, recordSeq, false)); |
|
} else { // handshake record |
|
|
|
while (plaintextFragment.remaining() > 0) { |
|
|
|
HandshakeFragment hsFrag = parseHandshakeMessage( |
|
contentType, majorVersion, minorVersion, |
|
recordEnS, recordEpoch, recordSeq, plaintextFragment); |
|
|
|
if (hsFrag == null) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Invalid handshake message, discard it."); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
if (reassembler == null) { |
|
reassembler = new DTLSReassembler(recordEpoch); |
|
} |
|
|
|
reassembler.queueUpHandshake(hsFrag); |
|
} |
|
} |
|
|
|
// Completed the read of the full record. Acquire the reassembled |
|
|
|
if (reassembler != null) { |
|
Plaintext pt = reassembler.acquirePlaintext(); |
|
return pt == null ? null : new Plaintext[] { pt }; |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("The reassembler is not initialized yet."); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
@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() < headerSize) { |
|
return -1; |
|
} |
|
|
|
|
|
int pos = packet.position(); |
|
|
|
|
|
byte contentType = packet.get(pos); |
|
if (ContentType.valueOf(contentType) == null) { |
|
throw new SSLException( |
|
"Unrecognized SSL message, plaintext connection?"); |
|
} |
|
|
|
|
|
byte majorVersion = packet.get(pos + 1); |
|
byte minorVersion = packet.get(pos + 2); |
|
if (!ProtocolVersion.isNegotiable( |
|
majorVersion, minorVersion, true, false)) { |
|
throw new SSLException("Unrecognized record version " + |
|
ProtocolVersion.nameOf(majorVersion, minorVersion) + |
|
" , plaintext connection?"); |
|
} |
|
|
|
|
|
int fragLen = ((packet.get(pos + 11) & 0xFF) << 8) + |
|
(packet.get(pos + 12) & 0xFF) + headerSize; |
|
if (fragLen > Record.maxFragmentSize) { |
|
throw new SSLException( |
|
"Record overflow, fragment length (" + fragLen + |
|
") MUST not exceed " + Record.maxFragmentSize); |
|
} |
|
|
|
return fragLen; |
|
} |
|
|
|
private static HandshakeFragment parseHandshakeMessage( |
|
byte contentType, byte majorVersion, byte minorVersion, |
|
byte[] recordEnS, int recordEpoch, long recordSeq, |
|
ByteBuffer plaintextFragment) { |
|
|
|
int remaining = plaintextFragment.remaining(); |
|
if (remaining < handshakeHeaderSize) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.fine("Discard invalid record: " + |
|
"too small record to hold a handshake fragment"); |
|
} |
|
|
|
|
|
return null; |
|
} |
|
|
|
byte handshakeType = plaintextFragment.get(); |
|
int messageLength = |
|
((plaintextFragment.get() & 0xFF) << 16) | |
|
((plaintextFragment.get() & 0xFF) << 8) | |
|
(plaintextFragment.get() & 0xFF); |
|
int messageSeq = |
|
((plaintextFragment.get() & 0xFF) << 8) | |
|
(plaintextFragment.get() & 0xFF); |
|
int fragmentOffset = |
|
((plaintextFragment.get() & 0xFF) << 16) | |
|
((plaintextFragment.get() & 0xFF) << 8) | |
|
(plaintextFragment.get() & 0xFF); |
|
int fragmentLength = |
|
((plaintextFragment.get() & 0xFF) << 16) | |
|
((plaintextFragment.get() & 0xFF) << 8) | |
|
(plaintextFragment.get() & 0xFF); |
|
if ((remaining - handshakeHeaderSize) < fragmentLength) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.fine("Discard invalid record: " + |
|
"not a complete handshake fragment in the record"); |
|
} |
|
|
|
|
|
return null; |
|
} |
|
|
|
byte[] fragment = new byte[fragmentLength]; |
|
plaintextFragment.get(fragment); |
|
|
|
return new HandshakeFragment(fragment, contentType, |
|
majorVersion, minorVersion, |
|
recordEnS, recordEpoch, recordSeq, |
|
handshakeType, messageLength, |
|
messageSeq, fragmentOffset, fragmentLength); |
|
} |
|
|
|
|
|
private static class RecordFragment implements Comparable<RecordFragment> { |
|
boolean isCiphertext; |
|
|
|
byte contentType; |
|
byte majorVersion; |
|
byte minorVersion; |
|
int recordEpoch; |
|
long recordSeq; |
|
byte[] recordEnS; |
|
byte[] fragment; |
|
|
|
RecordFragment(ByteBuffer fragBuf, byte contentType, |
|
byte majorVersion, byte minorVersion, byte[] recordEnS, |
|
int recordEpoch, long recordSeq, boolean isCiphertext) { |
|
this((byte[])null, contentType, majorVersion, minorVersion, |
|
recordEnS, recordEpoch, recordSeq, isCiphertext); |
|
|
|
this.fragment = new byte[fragBuf.remaining()]; |
|
fragBuf.get(this.fragment); |
|
} |
|
|
|
RecordFragment(byte[] fragment, byte contentType, |
|
byte majorVersion, byte minorVersion, byte[] recordEnS, |
|
int recordEpoch, long recordSeq, boolean isCiphertext) { |
|
this.isCiphertext = isCiphertext; |
|
|
|
this.contentType = contentType; |
|
this.majorVersion = majorVersion; |
|
this.minorVersion = minorVersion; |
|
this.recordEpoch = recordEpoch; |
|
this.recordSeq = recordSeq; |
|
this.recordEnS = recordEnS; |
|
this.fragment = fragment; |
|
// the buffer if necessary. |
|
} |
|
|
|
@Override |
|
public int compareTo(RecordFragment o) { |
|
if (this.contentType == ContentType.CHANGE_CIPHER_SPEC.id) { |
|
if (o.contentType == ContentType.CHANGE_CIPHER_SPEC.id) { |
|
// Only one incoming ChangeCipherSpec message for an epoch. |
|
// |
|
|
|
return Integer.compare(this.recordEpoch, o.recordEpoch); |
|
} else if ((this.recordEpoch == o.recordEpoch) && |
|
(o.contentType == ContentType.HANDSHAKE.id)) { |
|
|
|
return 1; |
|
} |
|
} else if (o.contentType == ContentType.CHANGE_CIPHER_SPEC.id) { |
|
if ((this.recordEpoch == o.recordEpoch) && |
|
(this.contentType == ContentType.HANDSHAKE.id)) { |
|
|
|
return -1; |
|
} else { |
|
|
|
return compareToSequence(o.recordEpoch, o.recordSeq); |
|
} |
|
} |
|
|
|
return compareToSequence(o.recordEpoch, o.recordSeq); |
|
} |
|
|
|
int compareToSequence(int epoch, long seq) { |
|
if (this.recordEpoch > epoch) { |
|
return 1; |
|
} else if (this.recordEpoch == epoch) { |
|
return Long.compare(this.recordSeq, seq); |
|
} else { |
|
return -1; |
|
} |
|
} |
|
} |
|
|
|
|
|
private static final class HandshakeFragment extends RecordFragment { |
|
|
|
byte handshakeType; |
|
int messageSeq; |
|
int messageLength; |
|
int fragmentOffset; |
|
int fragmentLength; |
|
|
|
HandshakeFragment(byte[] fragment, byte contentType, |
|
byte majorVersion, byte minorVersion, byte[] recordEnS, |
|
int recordEpoch, long recordSeq, |
|
byte handshakeType, int messageLength, |
|
int messageSeq, int fragmentOffset, int fragmentLength) { |
|
|
|
super(fragment, contentType, majorVersion, minorVersion, |
|
recordEnS, recordEpoch , recordSeq, false); |
|
|
|
this.handshakeType = handshakeType; |
|
this.messageSeq = messageSeq; |
|
this.messageLength = messageLength; |
|
this.fragmentOffset = fragmentOffset; |
|
this.fragmentLength = fragmentLength; |
|
} |
|
|
|
@Override |
|
public int compareTo(RecordFragment o) { |
|
if (o instanceof HandshakeFragment) { |
|
HandshakeFragment other = (HandshakeFragment)o; |
|
if (this.messageSeq != other.messageSeq) { |
|
|
|
return this.messageSeq - other.messageSeq; |
|
} else if (this.fragmentOffset != other.fragmentOffset) { |
|
|
|
return this.fragmentOffset - other.fragmentOffset; |
|
} else if (this.fragmentLength == other.fragmentLength) { |
|
|
|
return 0; |
|
} |
|
|
|
// Should be repacked for suitable fragment length. |
|
// |
|
// Note that the acquiring processes will reassemble |
|
|
|
return compareToSequence(o.recordEpoch, o.recordSeq); |
|
} |
|
|
|
return super.compareTo(o); |
|
} |
|
} |
|
|
|
private static final class HoleDescriptor { |
|
int offset; |
|
int limit; |
|
|
|
HoleDescriptor(int offset, int limit) { |
|
this.offset = offset; |
|
this.limit = limit; |
|
} |
|
} |
|
|
|
private static final class HandshakeFlight implements Cloneable { |
|
static final byte HF_UNKNOWN = SSLHandshake.NOT_APPLICABLE.id; |
|
|
|
byte handshakeType; |
|
int flightEpoch; |
|
int minMessageSeq; |
|
|
|
int maxMessageSeq; |
|
int maxRecordEpoch; |
|
long maxRecordSeq; |
|
|
|
HashMap<Byte, List<HoleDescriptor>> holesMap; |
|
|
|
HandshakeFlight() { |
|
this.handshakeType = HF_UNKNOWN; |
|
this.flightEpoch = 0; |
|
this.minMessageSeq = 0; |
|
|
|
this.maxMessageSeq = 0; |
|
this.maxRecordEpoch = 0; |
|
this.maxRecordSeq = -1; |
|
|
|
this.holesMap = new HashMap<>(5); |
|
} |
|
|
|
boolean isRetransmitOf(HandshakeFlight hs) { |
|
return (hs != null) && |
|
(this.handshakeType == hs.handshakeType) && |
|
(this.minMessageSeq == hs.minMessageSeq); |
|
} |
|
|
|
@Override |
|
public Object clone() { |
|
HandshakeFlight hf = new HandshakeFlight(); |
|
|
|
hf.handshakeType = this.handshakeType; |
|
hf.flightEpoch = this.flightEpoch; |
|
hf.minMessageSeq = this.minMessageSeq; |
|
|
|
hf.maxMessageSeq = this.maxMessageSeq; |
|
hf.maxRecordEpoch = this.maxRecordEpoch; |
|
hf.maxRecordSeq = this.maxRecordSeq; |
|
|
|
hf.holesMap = new HashMap<>(this.holesMap); |
|
|
|
return hf; |
|
} |
|
} |
|
|
|
final class DTLSReassembler { |
|
|
|
final int handshakeEpoch; |
|
|
|
|
|
TreeSet<RecordFragment> bufferedFragments = new TreeSet<>(); |
|
|
|
|
|
HandshakeFlight handshakeFlight = new HandshakeFlight(); |
|
|
|
|
|
HandshakeFlight precedingFlight = null; |
|
|
|
// Epoch, sequence number and handshake message sequence of the |
|
// next message acquisition of a flight. |
|
int nextRecordEpoch; |
|
long nextRecordSeq = 0; |
|
|
|
|
|
boolean expectCCSFlight = false; |
|
|
|
|
|
boolean flightIsReady = false; |
|
boolean needToCheckFlight = false; |
|
|
|
DTLSReassembler(int handshakeEpoch) { |
|
this.handshakeEpoch = handshakeEpoch; |
|
this.nextRecordEpoch = handshakeEpoch; |
|
|
|
this.handshakeFlight.flightEpoch = handshakeEpoch; |
|
} |
|
|
|
void expectingFinishFlight() { |
|
expectCCSFlight = true; |
|
} |
|
|
|
|
|
void queueUpHandshake(HandshakeFragment hsf) { |
|
if (!isDesirable(hsf)) { |
|
|
|
return; |
|
} |
|
|
|
|
|
cleanUpRetransmit(hsf); |
|
|
|
// Is it the first message of next flight? |
|
// |
|
|
|
boolean isMinimalFlightMessage = false; |
|
if (handshakeFlight.minMessageSeq == hsf.messageSeq) { |
|
isMinimalFlightMessage = true; |
|
} else if ((precedingFlight != null) && |
|
(precedingFlight.minMessageSeq == hsf.messageSeq)) { |
|
isMinimalFlightMessage = true; |
|
} |
|
|
|
if (isMinimalFlightMessage && (hsf.fragmentOffset == 0) && |
|
(hsf.handshakeType != SSLHandshake.FINISHED.id)) { |
|
|
|
|
|
handshakeFlight.handshakeType = hsf.handshakeType; |
|
handshakeFlight.flightEpoch = hsf.recordEpoch; |
|
handshakeFlight.minMessageSeq = hsf.messageSeq; |
|
} |
|
|
|
if (hsf.handshakeType == SSLHandshake.FINISHED.id) { |
|
handshakeFlight.maxMessageSeq = hsf.messageSeq; |
|
handshakeFlight.maxRecordEpoch = hsf.recordEpoch; |
|
handshakeFlight.maxRecordSeq = hsf.recordSeq; |
|
} else { |
|
if (handshakeFlight.maxMessageSeq < hsf.messageSeq) { |
|
handshakeFlight.maxMessageSeq = hsf.messageSeq; |
|
} |
|
|
|
int n = (hsf.recordEpoch - handshakeFlight.maxRecordEpoch); |
|
if (n > 0) { |
|
handshakeFlight.maxRecordEpoch = hsf.recordEpoch; |
|
handshakeFlight.maxRecordSeq = hsf.recordSeq; |
|
} else if (n == 0) { |
|
|
|
if (handshakeFlight.maxRecordSeq < hsf.recordSeq) { |
|
handshakeFlight.maxRecordSeq = hsf.recordSeq; |
|
} |
|
} // Otherwise, it is unlikely to happen. |
|
} |
|
|
|
boolean fragmented = false; |
|
if ((hsf.fragmentOffset) != 0 || |
|
(hsf.fragmentLength != hsf.messageLength)) { |
|
|
|
fragmented = true; |
|
} |
|
|
|
List<HoleDescriptor> holes = |
|
handshakeFlight.holesMap.get(hsf.handshakeType); |
|
if (holes == null) { |
|
if (!fragmented) { |
|
holes = Collections.emptyList(); |
|
} else { |
|
holes = new LinkedList<HoleDescriptor>(); |
|
holes.add(new HoleDescriptor(0, hsf.messageLength)); |
|
} |
|
handshakeFlight.holesMap.put(hsf.handshakeType, holes); |
|
} else if (holes.isEmpty()) { |
|
// Have got the full handshake message. This record may be |
|
// a handshake message retransmission. Discard this record. |
|
// |
|
// It's OK to discard retransmission as the handshake hash |
|
// is computed as if each handshake message had been sent |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("Have got the full message, discard it."); |
|
} |
|
|
|
return; |
|
} |
|
|
|
if (fragmented) { |
|
int fragmentLimit = hsf.fragmentOffset + hsf.fragmentLength; |
|
for (int i = 0; i < holes.size(); i++) { |
|
|
|
HoleDescriptor hole = holes.get(i); |
|
if ((hole.limit <= hsf.fragmentOffset) || |
|
(hole.offset >= fragmentLimit)) { |
|
|
|
continue; |
|
} |
|
|
|
|
|
if (((hole.offset > hsf.fragmentOffset) && |
|
(hole.offset < fragmentLimit)) || |
|
((hole.limit > hsf.fragmentOffset) && |
|
(hole.limit < fragmentLimit))) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.fine("Discard invalid record: " + |
|
"handshake fragment ranges are overlapping"); |
|
} |
|
|
|
|
|
return; |
|
} |
|
|
|
|
|
holes.remove(i); |
|
// i--; |
|
|
|
if (hsf.fragmentOffset > hole.offset) { |
|
holes.add(new HoleDescriptor( |
|
hole.offset, hsf.fragmentOffset)); |
|
// i++; |
|
} |
|
|
|
if (fragmentLimit < hole.limit) { |
|
holes.add(new HoleDescriptor( |
|
fragmentLimit, hole.limit)); |
|
// i++; |
|
} |
|
|
|
|
|
break; |
|
} |
|
} |
|
|
|
|
|
if (hsf.handshakeType == SSLHandshake.FINISHED.id) { |
|
|
|
bufferedFragments.add(hsf); |
|
} else { |
|
bufferFragment(hsf); |
|
} |
|
} |
|
|
|
|
|
void queueUpChangeCipherSpec(RecordFragment rf) { |
|
if (!isDesirable(rf)) { |
|
|
|
return; |
|
} |
|
|
|
|
|
cleanUpRetransmit(rf); |
|
|
|
// Is it the first message of this flight? |
|
// |
|
|
|
if (expectCCSFlight) { |
|
handshakeFlight.handshakeType = HandshakeFlight.HF_UNKNOWN; |
|
handshakeFlight.flightEpoch = rf.recordEpoch; |
|
} |
|
|
|
|
|
if (handshakeFlight.maxRecordSeq < rf.recordSeq) { |
|
handshakeFlight.maxRecordSeq = rf.recordSeq; |
|
} |
|
|
|
|
|
bufferFragment(rf); |
|
} |
|
|
|
// Queue up a ciphertext message. |
|
// |
|
|
|
void queueUpFragment(RecordFragment rf) { |
|
if (!isDesirable(rf)) { |
|
|
|
return; |
|
} |
|
|
|
|
|
cleanUpRetransmit(rf); |
|
|
|
|
|
bufferFragment(rf); |
|
} |
|
|
|
private void bufferFragment(RecordFragment rf) { |
|
|
|
bufferedFragments.add(rf); |
|
|
|
if (flightIsReady) { |
|
flightIsReady = false; |
|
} |
|
|
|
if (!needToCheckFlight) { |
|
needToCheckFlight = true; |
|
} |
|
} |
|
|
|
private void cleanUpRetransmit(RecordFragment rf) { |
|
|
|
boolean isNewFlight = false; |
|
if (precedingFlight != null) { |
|
if (precedingFlight.flightEpoch < rf.recordEpoch) { |
|
isNewFlight = true; |
|
} else { |
|
if (rf instanceof HandshakeFragment) { |
|
HandshakeFragment hsf = (HandshakeFragment)rf; |
|
if (precedingFlight.maxMessageSeq < hsf.messageSeq) { |
|
isNewFlight = true; |
|
} |
|
} else if ( |
|
rf.contentType != ContentType.CHANGE_CIPHER_SPEC.id) { |
|
|
|
|
|
if (precedingFlight.maxRecordEpoch < rf.recordEpoch) { |
|
isNewFlight = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (!isNewFlight) { |
|
|
|
return; |
|
} |
|
|
|
|
|
for (Iterator<RecordFragment> it = bufferedFragments.iterator(); |
|
it.hasNext();) { |
|
|
|
RecordFragment frag = it.next(); |
|
boolean isOld = false; |
|
if (frag.recordEpoch < precedingFlight.maxRecordEpoch) { |
|
isOld = true; |
|
} else if (frag.recordEpoch == precedingFlight.maxRecordEpoch) { |
|
if (frag.recordSeq <= precedingFlight.maxRecordSeq) { |
|
isOld = true; |
|
} |
|
} |
|
|
|
if (!isOld && (frag instanceof HandshakeFragment)) { |
|
HandshakeFragment hsf = (HandshakeFragment)frag; |
|
isOld = (hsf.messageSeq <= precedingFlight.maxMessageSeq); |
|
} |
|
|
|
if (isOld) { |
|
it.remove(); |
|
} else { |
|
|
|
break; |
|
} |
|
} |
|
|
|
|
|
precedingFlight = null; |
|
} |
|
|
|
// Is a desired record? |
|
// |
|
|
|
private boolean isDesirable(RecordFragment rf) { |
|
// |
|
// Discard records old than the previous epoch. |
|
|
|
int previousEpoch = nextRecordEpoch - 1; |
|
if (rf.recordEpoch < previousEpoch) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Too old epoch to use this record, discard it."); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// |
|
// Allow retransmission of last flight of the previous epoch |
|
// |
|
// For example, the last server delivered flight for session |
|
// resuming abbreviated handshaking consist three messages: |
|
// ServerHello |
|
// [ChangeCipherSpec] |
|
// Finished |
|
// |
|
// The epoch number is incremented and the sequence number is reset |
|
|
|
if (rf.recordEpoch == previousEpoch) { |
|
boolean isDesired = true; |
|
if (precedingFlight == null) { |
|
isDesired = false; |
|
} else { |
|
if (rf instanceof HandshakeFragment) { |
|
HandshakeFragment hsf = (HandshakeFragment)rf; |
|
if (precedingFlight.minMessageSeq > hsf.messageSeq) { |
|
isDesired = false; |
|
} |
|
} else if ( |
|
rf.contentType == ContentType.CHANGE_CIPHER_SPEC.id) { |
|
|
|
|
|
if (precedingFlight.flightEpoch != rf.recordEpoch) { |
|
isDesired = false; |
|
} |
|
} else { |
|
if ((rf.recordEpoch < precedingFlight.maxRecordEpoch) || |
|
(rf.recordEpoch == precedingFlight.maxRecordEpoch && |
|
rf.recordSeq <= precedingFlight.maxRecordSeq)) { |
|
isDesired = false; |
|
} |
|
} |
|
} |
|
|
|
if (!isDesired) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Too old retransmission to use, discard it."); |
|
} |
|
|
|
return false; |
|
} |
|
} else if ((rf.recordEpoch == nextRecordEpoch) && |
|
(nextRecordSeq > rf.recordSeq)) { |
|
|
|
// Previously disordered record for the current epoch. |
|
// |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Lagging behind record (sequence), discard it."); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
private boolean isEmpty() { |
|
return (bufferedFragments.isEmpty() || |
|
(!flightIsReady && !needToCheckFlight) || |
|
(needToCheckFlight && !flightIsReady())); |
|
} |
|
|
|
Plaintext acquirePlaintext() { |
|
if (bufferedFragments.isEmpty()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("No received handshake messages"); |
|
} |
|
return null; |
|
} |
|
|
|
if (!flightIsReady && needToCheckFlight) { |
|
|
|
flightIsReady = flightIsReady(); |
|
|
|
|
|
if (flightIsReady) { |
|
// Retransmitted handshake messages are not needed for |
|
|
|
if (handshakeFlight.isRetransmitOf(precedingFlight)) { |
|
|
|
bufferedFragments.clear(); |
|
|
|
|
|
resetHandshakeFlight(precedingFlight); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("Received a retransmission flight."); |
|
} |
|
|
|
return Plaintext.PLAINTEXT_NULL; |
|
} |
|
} |
|
|
|
needToCheckFlight = false; |
|
} |
|
|
|
if (!flightIsReady) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"The handshake flight is not ready to use: " + |
|
handshakeFlight.handshakeType); |
|
} |
|
return null; |
|
} |
|
|
|
RecordFragment rFrag = bufferedFragments.first(); |
|
Plaintext plaintext; |
|
if (!rFrag.isCiphertext) { |
|
|
|
plaintext = acquireHandshakeMessage(); |
|
|
|
|
|
if (bufferedFragments.isEmpty()) { |
|
// Need not to backup the holes map. Clear up it at first. |
|
handshakeFlight.holesMap.clear(); |
|
|
|
|
|
precedingFlight = (HandshakeFlight)handshakeFlight.clone(); |
|
|
|
|
|
resetHandshakeFlight(precedingFlight); |
|
|
|
if (expectCCSFlight && |
|
(precedingFlight.handshakeType == |
|
HandshakeFlight.HF_UNKNOWN)) { |
|
expectCCSFlight = false; |
|
} |
|
} |
|
} else { |
|
|
|
plaintext = acquireCachedMessage(); |
|
} |
|
|
|
return plaintext; |
|
} |
|
|
|
// |
|
// Reset the handshake flight from a previous one. |
|
|
|
private void resetHandshakeFlight(HandshakeFlight prev) { |
|
|
|
handshakeFlight.handshakeType = HandshakeFlight.HF_UNKNOWN; |
|
handshakeFlight.flightEpoch = prev.maxRecordEpoch; |
|
if (prev.flightEpoch != prev.maxRecordEpoch) { |
|
|
|
handshakeFlight.minMessageSeq = 0; |
|
} else { |
|
// stay at the same epoch |
|
// |
|
// The minimal message sequence number will get updated if |
|
|
|
handshakeFlight.minMessageSeq = prev.maxMessageSeq + 1; |
|
} |
|
|
|
// cleanup the maximum sequence number and epoch number. |
|
// |
|
// Note: actually, we need to do nothing because the reassembler |
|
// of handshake messages will reset them properly even for |
|
// retransmissions. |
|
|
|
handshakeFlight.maxMessageSeq = 0; |
|
handshakeFlight.maxRecordEpoch = handshakeFlight.flightEpoch; |
|
|
|
|
|
handshakeFlight.maxRecordSeq = prev.maxRecordSeq + 1; |
|
|
|
|
|
handshakeFlight.holesMap.clear(); |
|
|
|
|
|
flightIsReady = false; |
|
needToCheckFlight = false; |
|
} |
|
|
|
private Plaintext acquireCachedMessage() { |
|
RecordFragment rFrag = bufferedFragments.first(); |
|
if (readEpoch != rFrag.recordEpoch) { |
|
if (readEpoch > rFrag.recordEpoch) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Discard old buffered ciphertext fragments."); |
|
} |
|
bufferedFragments.remove(rFrag); |
|
} |
|
|
|
|
|
if (flightIsReady) { |
|
flightIsReady = false; |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Not yet ready to decrypt the cached fragments."); |
|
} |
|
return null; |
|
} |
|
|
|
bufferedFragments.remove(rFrag); |
|
|
|
ByteBuffer fragment = ByteBuffer.wrap(rFrag.fragment); |
|
ByteBuffer plaintextFragment = null; |
|
try { |
|
Plaintext plaintext = readCipher.decrypt( |
|
rFrag.contentType, fragment, rFrag.recordEnS); |
|
plaintextFragment = plaintext.fragment; |
|
rFrag.contentType = plaintext.contentType; |
|
} catch (GeneralSecurityException gse) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("Discard invalid record: ", gse); |
|
} |
|
|
|
|
|
return null; |
|
} |
|
|
|
// The ciphtext handshake message can only be Finished (the |
|
// end of this flight), ClinetHello or HelloRequest (the |
|
// beginning of the next flight) message. Need not to check |
|
|
|
if (rFrag.contentType == ContentType.HANDSHAKE.id) { |
|
while (plaintextFragment.remaining() > 0) { |
|
HandshakeFragment hsFrag = parseHandshakeMessage( |
|
rFrag.contentType, |
|
rFrag.majorVersion, rFrag.minorVersion, |
|
rFrag.recordEnS, rFrag.recordEpoch, rFrag.recordSeq, |
|
plaintextFragment); |
|
|
|
if (hsFrag == null) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Invalid handshake fragment, discard it", |
|
plaintextFragment); |
|
} |
|
return null; |
|
} |
|
|
|
queueUpHandshake(hsFrag); |
|
// The flight ready status (flightIsReady) should have |
|
// been checked and updated for the Finished handshake |
|
// message before the decryption. Please don't update |
|
|
|
if (hsFrag.handshakeType != SSLHandshake.FINISHED.id) { |
|
flightIsReady = false; |
|
needToCheckFlight = true; |
|
} |
|
} |
|
|
|
return acquirePlaintext(); |
|
} else { |
|
return new Plaintext(rFrag.contentType, |
|
rFrag.majorVersion, rFrag.minorVersion, |
|
rFrag.recordEpoch, |
|
Authenticator.toLong(rFrag.recordEnS), |
|
plaintextFragment); |
|
} |
|
} |
|
|
|
private Plaintext acquireHandshakeMessage() { |
|
|
|
RecordFragment rFrag = bufferedFragments.first(); |
|
if (rFrag.contentType == ContentType.CHANGE_CIPHER_SPEC.id) { |
|
this.nextRecordEpoch = rFrag.recordEpoch + 1; |
|
|
|
// For retransmissions, the next record sequence number is a |
|
// positive value. Don't worry about it as the acquiring of |
|
// the immediately followed Finished handshake message will |
|
|
|
this.nextRecordSeq = 0; |
|
|
|
|
|
bufferedFragments.remove(rFrag); |
|
return new Plaintext(rFrag.contentType, |
|
rFrag.majorVersion, rFrag.minorVersion, |
|
rFrag.recordEpoch, |
|
Authenticator.toLong(rFrag.recordEnS), |
|
ByteBuffer.wrap(rFrag.fragment)); |
|
} else { |
|
HandshakeFragment hsFrag = (HandshakeFragment)rFrag; |
|
if ((hsFrag.messageLength == hsFrag.fragmentLength) && |
|
(hsFrag.fragmentOffset == 0)) { // no fragmentation |
|
|
|
bufferedFragments.remove(rFrag); |
|
|
|
|
|
this.nextRecordSeq = hsFrag.recordSeq + 1; |
|
|
|
|
|
byte[] recordFrag = new byte[hsFrag.messageLength + 4]; |
|
Plaintext plaintext = new Plaintext( |
|
hsFrag.contentType, |
|
hsFrag.majorVersion, hsFrag.minorVersion, |
|
hsFrag.recordEpoch, |
|
Authenticator.toLong(hsFrag.recordEnS), |
|
ByteBuffer.wrap(recordFrag)); |
|
|
|
|
|
recordFrag[0] = hsFrag.handshakeType; |
|
recordFrag[1] = |
|
(byte)((hsFrag.messageLength >>> 16) & 0xFF); |
|
recordFrag[2] = |
|
(byte)((hsFrag.messageLength >>> 8) & 0xFF); |
|
recordFrag[3] = (byte)(hsFrag.messageLength & 0xFF); |
|
|
|
System.arraycopy(hsFrag.fragment, 0, |
|
recordFrag, 4, hsFrag.fragmentLength); |
|
|
|
|
|
handshakeHashing(hsFrag, plaintext); |
|
|
|
return plaintext; |
|
} else { // fragmented handshake message |
|
// the first record |
|
// |
|
|
|
byte[] recordFrag = new byte[hsFrag.messageLength + 4]; |
|
Plaintext plaintext = new Plaintext( |
|
hsFrag.contentType, |
|
hsFrag.majorVersion, hsFrag.minorVersion, |
|
hsFrag.recordEpoch, |
|
Authenticator.toLong(hsFrag.recordEnS), |
|
ByteBuffer.wrap(recordFrag)); |
|
|
|
|
|
recordFrag[0] = hsFrag.handshakeType; |
|
recordFrag[1] = |
|
(byte)((hsFrag.messageLength >>> 16) & 0xFF); |
|
recordFrag[2] = |
|
(byte)((hsFrag.messageLength >>> 8) & 0xFF); |
|
recordFrag[3] = (byte)(hsFrag.messageLength & 0xFF); |
|
|
|
int msgSeq = hsFrag.messageSeq; |
|
long maxRecodeSN = hsFrag.recordSeq; |
|
HandshakeFragment hmFrag = hsFrag; |
|
do { |
|
System.arraycopy(hmFrag.fragment, 0, |
|
recordFrag, hmFrag.fragmentOffset + 4, |
|
hmFrag.fragmentLength); |
|
|
|
bufferedFragments.remove(rFrag); |
|
|
|
if (maxRecodeSN < hmFrag.recordSeq) { |
|
maxRecodeSN = hmFrag.recordSeq; |
|
} |
|
|
|
// Note: may buffer retransmitted fragments in order to |
|
// speed up the reassembly in the future. |
|
|
|
|
|
if (!bufferedFragments.isEmpty()) { |
|
rFrag = bufferedFragments.first(); |
|
if (rFrag.contentType != ContentType.HANDSHAKE.id) { |
|
break; |
|
} else { |
|
hmFrag = (HandshakeFragment)rFrag; |
|
} |
|
} |
|
} while (!bufferedFragments.isEmpty() && |
|
(msgSeq == hmFrag.messageSeq)); |
|
|
|
|
|
handshakeHashing(hsFrag, plaintext); |
|
|
|
this.nextRecordSeq = maxRecodeSN + 1; |
|
|
|
return plaintext; |
|
} |
|
} |
|
} |
|
|
|
boolean flightIsReady() { |
|
|
|
byte flightType = handshakeFlight.handshakeType; |
|
if (flightType == HandshakeFlight.HF_UNKNOWN) { |
|
// |
|
// the ChangeCipherSpec/Finished flight |
|
|
|
if (expectCCSFlight) { |
|
|
|
boolean isReady = hasFinishedMessage(bufferedFragments); |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Has the final flight been received? " + isReady); |
|
} |
|
|
|
return isReady; |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("No flight is received yet."); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
if ((flightType == SSLHandshake.CLIENT_HELLO.id) || |
|
(flightType == SSLHandshake.HELLO_REQUEST.id) || |
|
(flightType == SSLHandshake.HELLO_VERIFY_REQUEST.id)) { |
|
|
|
|
|
boolean isReady = hasCompleted(flightType); |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Is the handshake message completed? " + isReady); |
|
} |
|
|
|
return isReady; |
|
} |
|
|
|
// |
|
// the ServerHello flight |
|
|
|
if (flightType == SSLHandshake.SERVER_HELLO.id) { |
|
|
|
if (!hasCompleted(flightType)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"The ServerHello message is not completed yet."); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// |
|
// an abbreviated handshake |
|
|
|
if (hasFinishedMessage(bufferedFragments)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("It's an abbreviated handshake."); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// |
|
// a full handshake |
|
|
|
List<HoleDescriptor> holes = handshakeFlight.holesMap.get( |
|
SSLHandshake.SERVER_HELLO_DONE.id); |
|
if ((holes == null) || !holes.isEmpty()) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Not yet got the ServerHelloDone message"); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
boolean isReady = hasCompleted(bufferedFragments, |
|
handshakeFlight.minMessageSeq, |
|
handshakeFlight.maxMessageSeq); |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Is the ServerHello flight (message " + |
|
handshakeFlight.minMessageSeq + "-" + |
|
handshakeFlight.maxMessageSeq + |
|
") completed? " + isReady); |
|
} |
|
|
|
return isReady; |
|
} |
|
|
|
// |
|
// the ClientKeyExchange flight |
|
// |
|
// Note: need to consider more messages in this flight if |
|
// ht_supplemental_data and ht_certificate_url are |
|
// suppported in the future. |
|
|
|
if ((flightType == SSLHandshake.CERTIFICATE.id) || |
|
(flightType == SSLHandshake.CLIENT_KEY_EXCHANGE.id)) { |
|
|
|
|
|
if (!hasCompleted(flightType)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"The ClientKeyExchange or client Certificate " + |
|
"message is not completed yet."); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
if (flightType == SSLHandshake.CERTIFICATE.id) { |
|
if (needClientVerify(bufferedFragments) && |
|
!hasCompleted(SSLHandshake.CERTIFICATE_VERIFY.id)) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Not yet have the CertificateVerify message"); |
|
} |
|
|
|
return false; |
|
} |
|
} |
|
|
|
if (!hasFinishedMessage(bufferedFragments)) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Not yet have the ChangeCipherSpec and " + |
|
"Finished messages"); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
boolean isReady = hasCompleted(bufferedFragments, |
|
handshakeFlight.minMessageSeq, |
|
handshakeFlight.maxMessageSeq); |
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine( |
|
"Is the ClientKeyExchange flight (message " + |
|
handshakeFlight.minMessageSeq + "-" + |
|
handshakeFlight.maxMessageSeq + |
|
") completed? " + isReady); |
|
} |
|
|
|
return isReady; |
|
} |
|
|
|
// |
|
// Otherwise, need to receive more handshake messages. |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { |
|
SSLLogger.fine("Need to receive more handshake messages"); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// Looking for the ChangeCipherSpec and Finished messages. |
|
// |
|
// As the cached Finished message should be a ciphertext, we don't |
|
// exactly know a ciphertext is a Finished message or not. According |
|
// to the spec of TLS/DTLS handshaking, a Finished message is always |
|
// sent immediately after a ChangeCipherSpec message. The first |
|
|
|
private boolean hasFinishedMessage(Set<RecordFragment> fragments) { |
|
|
|
boolean hasCCS = false; |
|
boolean hasFin = false; |
|
for (RecordFragment fragment : fragments) { |
|
if (fragment.contentType == ContentType.CHANGE_CIPHER_SPEC.id) { |
|
if (hasFin) { |
|
return true; |
|
} |
|
hasCCS = true; |
|
} else if (fragment.contentType == ContentType.HANDSHAKE.id) { |
|
|
|
if (fragment.isCiphertext) { |
|
if (hasCCS) { |
|
return true; |
|
} |
|
hasFin = true; |
|
} |
|
} |
|
} |
|
|
|
return hasFin && hasCCS; |
|
} |
|
|
|
// Is client CertificateVerify a mandatory message? |
|
// |
|
// In the current implementation, client CertificateVerify is a |
|
|
|
private boolean needClientVerify(Set<RecordFragment> fragments) { |
|
|
|
// The caller should have checked the completion of the first |
|
|
|
for (RecordFragment rFrag : fragments) { |
|
if ((rFrag.contentType != ContentType.HANDSHAKE.id) || |
|
rFrag.isCiphertext) { |
|
break; |
|
} |
|
|
|
HandshakeFragment hsFrag = (HandshakeFragment)rFrag; |
|
if (hsFrag.handshakeType != SSLHandshake.CERTIFICATE.id) { |
|
continue; |
|
} |
|
|
|
return (rFrag.fragment != null) && |
|
(rFrag.fragment.length > DTLSRecord.minCertPlaintextSize); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
private boolean hasCompleted(byte handshakeType) { |
|
List<HoleDescriptor> holes = |
|
handshakeFlight.holesMap.get(handshakeType); |
|
if (holes == null) { |
|
|
|
return false; |
|
} |
|
|
|
return holes.isEmpty(); |
|
} |
|
|
|
private boolean hasCompleted( |
|
Set<RecordFragment> fragments, |
|
int presentMsgSeq, int endMsgSeq) { |
|
|
|
// The caller should have checked the completion of the first |
|
|
|
for (RecordFragment rFrag : fragments) { |
|
if ((rFrag.contentType != ContentType.HANDSHAKE.id) || |
|
rFrag.isCiphertext) { |
|
break; |
|
} |
|
|
|
HandshakeFragment hsFrag = (HandshakeFragment)rFrag; |
|
if (hsFrag.messageSeq == presentMsgSeq) { |
|
continue; |
|
} else if (hsFrag.messageSeq == (presentMsgSeq + 1)) { |
|
|
|
if (!hasCompleted(hsFrag.handshakeType)) { |
|
return false; |
|
} |
|
|
|
presentMsgSeq = hsFrag.messageSeq; |
|
} else { |
|
|
|
break; |
|
} |
|
} |
|
|
|
return (presentMsgSeq >= endMsgSeq); |
|
// false: if not yet got all messages of the flight. |
|
} |
|
|
|
private void handshakeHashing( |
|
HandshakeFragment hsFrag, Plaintext plaintext) { |
|
byte hsType = hsFrag.handshakeType; |
|
if (!handshakeHash.isHashable(hsType)) { |
|
|
|
return; |
|
} |
|
|
|
// calculate the DTLS header and reserve the handshake message |
|
plaintext.fragment.position(4); |
|
byte[] temporary = new byte[plaintext.fragment.remaining() + 12]; |
|
// 12: handshake header size |
|
|
|
|
|
temporary[0] = hsFrag.handshakeType; |
|
|
|
|
|
temporary[1] = (byte)((hsFrag.messageLength >> 16) & 0xFF); |
|
temporary[2] = (byte)((hsFrag.messageLength >> 8) & 0xFF); |
|
temporary[3] = (byte)(hsFrag.messageLength & 0xFF); |
|
|
|
|
|
temporary[4] = (byte)((hsFrag.messageSeq >> 8) & 0xFF); |
|
temporary[5] = (byte)(hsFrag.messageSeq & 0xFF); |
|
|
|
|
|
temporary[6] = 0; |
|
temporary[7] = 0; |
|
temporary[8] = 0; |
|
|
|
|
|
temporary[9] = temporary[1]; |
|
temporary[10] = temporary[2]; |
|
temporary[11] = temporary[3]; |
|
|
|
plaintext.fragment.get(temporary, |
|
12, plaintext.fragment.remaining()); |
|
handshakeHash.receive(temporary); |
|
plaintext.fragment.position(0); |
|
} |
|
} |
|
} |
|
|