summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/ssl/dtlscon.c
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@gmail.com>2018-06-06 21:27:04 +0200
committerwolfbeast <mcwerewolf@gmail.com>2018-06-06 21:27:04 +0200
commit4a71b30364a4b6d1eaf16fcfdc8e873e6697f293 (patch)
treea47014077c14579249859ad34afcc5a8f2f0730a /security/nss/lib/ssl/dtlscon.c
parentd7da72799521386c110dbba73b1e483b00a0a56a (diff)
parent2dad0ec41d0b69c0a815012e6ea4bdde81b2875b (diff)
downloadUXP-4a71b30364a4b6d1eaf16fcfdc8e873e6697f293.tar
UXP-4a71b30364a4b6d1eaf16fcfdc8e873e6697f293.tar.gz
UXP-4a71b30364a4b6d1eaf16fcfdc8e873e6697f293.tar.lz
UXP-4a71b30364a4b6d1eaf16fcfdc8e873e6697f293.tar.xz
UXP-4a71b30364a4b6d1eaf16fcfdc8e873e6697f293.zip
Merge branch 'NSS-335'
Diffstat (limited to 'security/nss/lib/ssl/dtlscon.c')
-rw-r--r--security/nss/lib/ssl/dtlscon.c720
1 files changed, 444 insertions, 276 deletions
diff --git a/security/nss/lib/ssl/dtlscon.c b/security/nss/lib/ssl/dtlscon.c
index fbd1779db..2f335f924 100644
--- a/security/nss/lib/ssl/dtlscon.c
+++ b/security/nss/lib/ssl/dtlscon.c
@@ -10,16 +10,17 @@
#include "ssl.h"
#include "sslimpl.h"
#include "sslproto.h"
+#include "dtls13con.h"
#ifndef PR_ARRAY_SIZE
#define PR_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif
-static SECStatus dtls_TransmitMessageFlight(sslSocket *ss);
static SECStatus dtls_StartRetransmitTimer(sslSocket *ss);
static void dtls_RetransmitTimerExpiredCb(sslSocket *ss);
static SECStatus dtls_SendSavedWriteData(sslSocket *ss);
static void dtls_FinishedTimerCb(sslSocket *ss);
+static void dtls_CancelAllTimers(sslSocket *ss);
/* -28 adjusts for the IP/UDP header */
static const PRUint16 COMMON_MTU_VALUES[] = {
@@ -30,6 +31,9 @@ static const PRUint16 COMMON_MTU_VALUES[] = {
};
#define DTLS_COOKIE_BYTES 32
+/* Maximum DTLS expansion = header + IV + max CBC padding +
+ * maximum MAC. */
+#define DTLS_MAX_EXPANSION (DTLS_RECORD_HEADER_LENGTH + 16 + 16 + 32)
/* List copied from ssl3con.c:cipherSuites */
static const ssl3CipherSuite nonDTLSSuites[] = {
@@ -119,9 +123,9 @@ static DTLSQueuedMessage *
dtls_AllocQueuedMessage(ssl3CipherSpec *cwSpec, SSL3ContentType type,
const unsigned char *data, PRUint32 len)
{
- DTLSQueuedMessage *msg = NULL;
+ DTLSQueuedMessage *msg;
- msg = PORT_ZAlloc(sizeof(DTLSQueuedMessage));
+ msg = PORT_ZNew(DTLSQueuedMessage);
if (!msg)
return NULL;
@@ -137,7 +141,7 @@ dtls_AllocQueuedMessage(ssl3CipherSpec *cwSpec, SSL3ContentType type,
msg->type = type;
/* Safe if we are < 1.3, since the refct is
* already very high. */
- tls13_CipherSpecAddRef(cwSpec);
+ ssl_CipherSpecAddRef(cwSpec);
return msg;
}
@@ -155,7 +159,7 @@ dtls_FreeHandshakeMessage(DTLSQueuedMessage *msg)
/* Safe if we are < 1.3, since the refct is
* already very high. */
- tls13_CipherSpecRelease(msg->cwSpec);
+ ssl_CipherSpecRelease(msg->cwSpec);
PORT_ZFree(msg->data, msg->len);
PORT_Free(msg);
}
@@ -184,37 +188,38 @@ dtls_FreeHandshakeMessages(PRCList *list)
static SECStatus
dtls_RetransmitDetected(sslSocket *ss)
{
+ dtlsTimer *timer = ss->ssl3.hs.rtTimer;
SECStatus rv = SECSuccess;
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
- if (ss->ssl3.hs.rtTimerCb == dtls_RetransmitTimerExpiredCb) {
+ if (timer->cb == dtls_RetransmitTimerExpiredCb) {
/* Check to see if we retransmitted recently. If so,
* suppress the triggered retransmit. This avoids
* retransmit wars after packet loss.
* This is not in RFC 5346 but it should be.
*/
- if ((PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted) >
- (ss->ssl3.hs.rtTimeoutMs / 4)) {
+ if ((PR_IntervalNow() - timer->started) >
+ (timer->timeout / 4)) {
SSL_TRC(30,
("%d: SSL3[%d]: Shortcutting retransmit timer",
SSL_GETPID(), ss->fd));
/* Cancel the timer and call the CB,
* which re-arms the timer */
- dtls_CancelTimer(ss);
+ dtls_CancelTimer(ss, ss->ssl3.hs.rtTimer);
dtls_RetransmitTimerExpiredCb(ss);
} else {
SSL_TRC(30,
("%d: SSL3[%d]: Ignoring retransmission: "
"last retransmission %dms ago, suppressed for %dms",
SSL_GETPID(), ss->fd,
- PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted,
- ss->ssl3.hs.rtTimeoutMs / 4));
+ PR_IntervalNow() - timer->started,
+ timer->timeout / 4));
}
- } else if (ss->ssl3.hs.rtTimerCb == dtls_FinishedTimerCb) {
+ } else if (timer->cb == dtls_FinishedTimerCb) {
SSL_TRC(30, ("%d: SSL3[%d]: Retransmit detected in holddown",
SSL_GETPID(), ss->fd));
/* Retransmit the messages and re-arm the timer
@@ -222,14 +227,14 @@ dtls_RetransmitDetected(sslSocket *ss)
* The spec isn't clear and my reasoning is that this
* may be a re-ordered packet rather than slowness,
* so let's be aggressive. */
- dtls_CancelTimer(ss);
+ dtls_CancelTimer(ss, ss->ssl3.hs.rtTimer);
rv = dtls_TransmitMessageFlight(ss);
if (rv == SECSuccess) {
rv = dtls_StartHolddownTimer(ss);
}
} else {
- PORT_Assert(ss->ssl3.hs.rtTimerCb == NULL);
+ PORT_Assert(timer->cb == NULL);
/* ... and ignore it. */
}
return rv;
@@ -238,19 +243,8 @@ dtls_RetransmitDetected(sslSocket *ss)
static SECStatus
dtls_HandleHandshakeMessage(sslSocket *ss, PRUint8 *data, PRBool last)
{
-
- /* At this point we are advancing our state machine, so we can free our last
- * flight of messages. */
- dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
ss->ssl3.hs.recvdHighWater = -1;
- /* Reset the timer to the initial value if the retry counter
- * is 0, per Sec. 4.2.4.1 */
- dtls_CancelTimer(ss);
- if (ss->ssl3.hs.rtRetries == 0) {
- ss->ssl3.hs.rtTimeoutMs = DTLS_RETRANSMIT_INITIAL_MS;
- }
-
return ssl3_HandleHandshakeMessage(ss, data, ss->ssl3.hs.msg_len,
last);
}
@@ -273,7 +267,8 @@ dtls_HandleHandshakeMessage(sslSocket *ss, PRUint8 *data, PRBool last)
#define OFFSET_MASK(o) (1 << (o % 8))
SECStatus
-dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
+dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum,
+ sslBuffer *origBuf)
{
/* XXX OK for now.
* This doesn't work properly with asynchronous certificate validation.
@@ -283,6 +278,9 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
*/
sslBuffer buf = *origBuf;
SECStatus rv = SECSuccess;
+ PRBool discarded = PR_FALSE;
+
+ ss->ssl3.hs.endOfFlight = PR_FALSE;
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
@@ -298,7 +296,7 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
if (buf.len < 12) {
PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
rv = SECFailure;
- break;
+ goto loser;
}
/* Parse the header */
@@ -323,14 +321,28 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
if (buf.len < fragment_length) {
PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
rv = SECFailure;
- break;
+ goto loser;
}
/* Sanity check the packet contents */
if ((fragment_length + fragment_offset) > message_length) {
PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
rv = SECFailure;
- break;
+ goto loser;
+ }
+
+ /* If we're a server and we receive what appears to be a retried
+ * ClientHello, and we are expecting a ClientHello, move the receive
+ * sequence number forward. This allows for a retried ClientHello if we
+ * send a stateless HelloRetryRequest. */
+ if (message_seq > ss->ssl3.hs.recvMessageSeq &&
+ message_seq == 1 &&
+ fragment_offset == 0 &&
+ ss->ssl3.hs.ws == wait_client_hello &&
+ (SSLHandshakeType)type == ssl_hs_client_hello) {
+ SSL_TRC(5, ("%d: DTLS[%d]: Received apparent 2nd ClientHello",
+ SSL_GETPID(), ss->fd));
+ ss->ssl3.hs.recvMessageSeq = 1;
}
/* There are three ways we could not be ready for this packet.
@@ -346,20 +358,20 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
(fragment_offset == 0) &&
(fragment_length == message_length)) {
/* Complete next message. Process immediately */
- ss->ssl3.hs.msg_type = (SSL3HandshakeType)type;
+ ss->ssl3.hs.msg_type = (SSLHandshakeType)type;
ss->ssl3.hs.msg_len = message_length;
rv = dtls_HandleHandshakeMessage(ss, buf.buf,
buf.len == fragment_length);
if (rv == SECFailure) {
- break; /* Discard the remainder of the record. */
+ goto loser;
}
} else {
if (message_seq < ss->ssl3.hs.recvMessageSeq) {
/* Case 3: we do an immediate retransmit if we're
* in a waiting state. */
rv = dtls_RetransmitDetected(ss);
- break;
+ goto loser;
} else if (message_seq > ss->ssl3.hs.recvMessageSeq) {
/* Case 2
*
@@ -369,7 +381,12 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
*
* XXX OK for now. Maybe do something smarter at some point?
*/
+ SSL_TRC(10, ("%d: SSL3[%d]: dtls_HandleHandshake, discarding handshake message",
+ SSL_GETPID(), ss->fd));
+ discarded = PR_TRUE;
} else {
+ PRInt32 end = fragment_offset + fragment_length;
+
/* Case 1
*
* Buffer the fragment for reassembly
@@ -380,18 +397,18 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
rv = sslBuffer_Grow(&ss->ssl3.hs.msg_body, message_length);
if (rv != SECSuccess)
- break;
+ goto loser;
/* Make room for the fragment map */
rv = sslBuffer_Grow(&ss->ssl3.hs.recvdFragments,
map_length);
if (rv != SECSuccess)
- break;
+ goto loser;
/* Reset the reassembly map */
ss->ssl3.hs.recvdHighWater = 0;
PORT_Memset(ss->ssl3.hs.recvdFragments.buf, 0,
ss->ssl3.hs.recvdFragments.space);
- ss->ssl3.hs.msg_type = (SSL3HandshakeType)type;
+ ss->ssl3.hs.msg_type = (SSLHandshakeType)type;
ss->ssl3.hs.msg_len = message_length;
}
@@ -403,14 +420,14 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
ss->ssl3.hs.recvdHighWater = -1;
PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
rv = SECFailure;
- break;
+ goto loser;
}
- /* Now copy this fragment into the buffer */
- PORT_Assert((fragment_offset + fragment_length) <=
- ss->ssl3.hs.msg_body.space);
- PORT_Memcpy(ss->ssl3.hs.msg_body.buf + fragment_offset,
- buf.buf, fragment_length);
+ /* Now copy this fragment into the buffer. */
+ if (end > ss->ssl3.hs.recvdHighWater) {
+ PORT_Memcpy(ss->ssl3.hs.msg_body.buf + fragment_offset,
+ buf.buf, fragment_length);
+ }
/* This logic is a bit tricky. We have two values for
* reassembly state:
@@ -426,12 +443,11 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
if (fragment_offset <= (unsigned int)ss->ssl3.hs.recvdHighWater) {
/* Either this is the adjacent fragment or an overlapping
* fragment */
- ss->ssl3.hs.recvdHighWater = fragment_offset +
- fragment_length;
+ if (end > ss->ssl3.hs.recvdHighWater) {
+ ss->ssl3.hs.recvdHighWater = end;
+ }
} else {
- for (offset = fragment_offset;
- offset < fragment_offset + fragment_length;
- offset++) {
+ for (offset = fragment_offset; offset < end; offset++) {
ss->ssl3.hs.recvdFragments.buf[OFFSET_BYTE(offset)] |=
OFFSET_MASK(offset);
}
@@ -457,7 +473,7 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
buf.len == fragment_length);
if (rv == SECFailure) {
- break; /* Discard the rest of the record. */
+ goto loser;
}
}
}
@@ -467,6 +483,26 @@ dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
buf.len -= fragment_length;
}
+ // This should never happen, but belt and suspenders.
+ if (rv == SECFailure) {
+ PORT_Assert(0);
+ goto loser;
+ }
+
+ /* If we processed all the fragments in this message, then mark it as remembered.
+ * TODO(ekr@rtfm.com): Store out of order messages for DTLS 1.3 so ACKs work
+ * better. Bug 1392620.*/
+ if (!discarded && tls13_MaybeTls13(ss)) {
+ rv = dtls13_RememberFragment(ss, &ss->ssl3.hs.dtlsRcvdHandshake,
+ 0, 0, 0, epoch, seqNum);
+ }
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = dtls13_SetupAcks(ss);
+
+loser:
origBuf->len = 0; /* So ssl3_GatherAppDataRecord will keep looping. */
/* XXX OK for now. In future handle rv == SECWouldBlock safely in order
@@ -560,6 +596,8 @@ dtls_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags)
if (!(flags & ssl_SEND_FLAG_NO_RETRANSMIT)) {
rv = dtls_StartRetransmitTimer(ss);
+ } else {
+ PORT_Assert(ss->version < SSL_LIBRARY_VERSION_TLS_1_3);
}
}
@@ -576,7 +614,7 @@ static void
dtls_RetransmitTimerExpiredCb(sslSocket *ss)
{
SECStatus rv;
-
+ dtlsTimer *timer = ss->ssl3.hs.rtTimer;
ss->ssl3.hs.rtRetries++;
if (!(ss->ssl3.hs.rtRetries % 3)) {
@@ -589,175 +627,239 @@ dtls_RetransmitTimerExpiredCb(sslSocket *ss)
rv = dtls_TransmitMessageFlight(ss);
if (rv == SECSuccess) {
/* Re-arm the timer */
- ss->ssl3.hs.rtTimeoutMs *= 2;
- if (ss->ssl3.hs.rtTimeoutMs > DTLS_RETRANSMIT_MAX_MS) {
- ss->ssl3.hs.rtTimeoutMs = DTLS_RETRANSMIT_MAX_MS;
+ timer->timeout *= 2;
+ if (timer->timeout > DTLS_RETRANSMIT_MAX_MS) {
+ timer->timeout = DTLS_RETRANSMIT_MAX_MS;
}
- ss->ssl3.hs.rtTimerStarted = PR_IntervalNow();
- ss->ssl3.hs.rtTimerCb = dtls_RetransmitTimerExpiredCb;
+ timer->started = PR_IntervalNow();
+ timer->cb = dtls_RetransmitTimerExpiredCb;
SSL_TRC(30,
("%d: SSL3[%d]: Retransmit #%d, next in %d",
SSL_GETPID(), ss->fd,
- ss->ssl3.hs.rtRetries, ss->ssl3.hs.rtTimeoutMs));
+ ss->ssl3.hs.rtRetries, timer->timeout));
}
/* else: OK for now. In future maybe signal the stack that we couldn't
* transmit. For now, let the read handle any real network errors */
}
+#define DTLS_HS_HDR_LEN 12
+#define DTLS_MIN_FRAGMENT (DTLS_HS_HDR_LEN + 1 + DTLS_MAX_EXPANSION)
+
+/* Encrypt and encode a handshake message fragment. Flush the data out to the
+ * network if there is insufficient space for any fragment. */
+static SECStatus
+dtls_SendFragment(sslSocket *ss, DTLSQueuedMessage *msg, PRUint8 *data,
+ unsigned int len)
+{
+ PRInt32 sent;
+ SECStatus rv;
+
+ PRINT_BUF(40, (ss, "dtls_SendFragment", data, len));
+ sent = ssl3_SendRecord(ss, msg->cwSpec, msg->type, data, len,
+ ssl_SEND_FLAG_FORCE_INTO_BUFFER);
+ if (sent != len) {
+ if (sent != -1) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ }
+ return SECFailure;
+ }
+
+ /* If another fragment won't fit, flush. */
+ if (ss->ssl3.mtu < ss->pendingBuf.len + DTLS_MIN_FRAGMENT) {
+ SSL_TRC(20, ("%d: DTLS[%d]: dtls_SendFragment: flush",
+ SSL_GETPID(), ss->fd));
+ rv = dtls_SendSavedWriteData(ss);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ }
+ return SECSuccess;
+}
+
+/* Fragment a handshake message into multiple records and send them. */
+static SECStatus
+dtls_FragmentHandshake(sslSocket *ss, DTLSQueuedMessage *msg)
+{
+ PRBool fragmentWritten = PR_FALSE;
+ PRUint16 msgSeq;
+ PRUint8 *fragment;
+ PRUint32 fragmentOffset = 0;
+ PRUint32 fragmentLen;
+ const PRUint8 *content = msg->data + DTLS_HS_HDR_LEN;
+ PRUint32 contentLen = msg->len - DTLS_HS_HDR_LEN;
+ SECStatus rv;
+
+ /* The headers consume 12 bytes so the smallest possible message (i.e., an
+ * empty one) is 12 bytes. */
+ PORT_Assert(msg->len >= DTLS_HS_HDR_LEN);
+
+ /* DTLS only supports fragmenting handshaking messages. */
+ PORT_Assert(msg->type == content_handshake);
+
+ msgSeq = (msg->data[4] << 8) | msg->data[5];
+
+ /* do {} while() so that empty messages are sent at least once. */
+ do {
+ PRUint8 buf[DTLS_MAX_MTU]; /* >= than largest plausible MTU */
+ PRBool hasUnackedRange;
+ PRUint32 end;
+
+ hasUnackedRange = dtls_NextUnackedRange(ss, msgSeq,
+ fragmentOffset, contentLen,
+ &fragmentOffset, &end);
+ if (!hasUnackedRange) {
+ SSL_TRC(20, ("%d: SSL3[%d]: FragmentHandshake %d: all acknowledged",
+ SSL_GETPID(), ss->fd, msgSeq));
+ break;
+ }
+
+ SSL_TRC(20, ("%d: SSL3[%d]: FragmentHandshake %d: unacked=%u-%u",
+ SSL_GETPID(), ss->fd, msgSeq, fragmentOffset, end));
+
+ /* Cut down to the data we have available. */
+ PORT_Assert(fragmentOffset <= contentLen);
+ PORT_Assert(fragmentOffset <= end);
+ PORT_Assert(end <= contentLen);
+ fragmentLen = PR_MIN(end, contentLen) - fragmentOffset;
+
+ /* Reduce to the space remaining in the MTU. Allow for any existing
+ * messages, record expansion, and the handshake header. */
+ fragmentLen = PR_MIN(fragmentLen,
+ ss->ssl3.mtu - /* MTU estimate. */
+ ss->pendingBuf.len - /* Less unsent records. */
+ DTLS_MAX_EXPANSION - /* Allow for expansion. */
+ DTLS_HS_HDR_LEN); /* + handshake header. */
+ PORT_Assert(fragmentLen > 0 || fragmentOffset == 0);
+
+ /* Make totally sure that we will fit in the buffer. This should be
+ * impossible; DTLS_MAX_MTU should always be more than ss->ssl3.mtu. */
+ if (fragmentLen >= (DTLS_MAX_MTU - DTLS_HS_HDR_LEN)) {
+ PORT_Assert(0);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ if (fragmentLen == contentLen) {
+ fragment = msg->data;
+ } else {
+ sslBuffer tmp = SSL_BUFFER_FIXED(buf, sizeof(buf));
+
+ /* Construct an appropriate-sized fragment */
+ /* Type, length, sequence */
+ rv = sslBuffer_Append(&tmp, msg->data, 6);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ /* Offset. */
+ rv = sslBuffer_AppendNumber(&tmp, fragmentOffset, 3);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ /* Length. */
+ rv = sslBuffer_AppendNumber(&tmp, fragmentLen, 3);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ /* Data. */
+ rv = sslBuffer_Append(&tmp, content + fragmentOffset, fragmentLen);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ fragment = SSL_BUFFER_BASE(&tmp);
+ }
+
+ /* Record that we are sending first, because encrypting
+ * increments the sequence number. */
+ rv = dtls13_RememberFragment(ss, &ss->ssl3.hs.dtlsSentHandshake,
+ msgSeq, fragmentOffset, fragmentLen,
+ msg->cwSpec->epoch,
+ msg->cwSpec->seqNum);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ rv = dtls_SendFragment(ss, msg, fragment,
+ fragmentLen + DTLS_HS_HDR_LEN);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ fragmentWritten = PR_TRUE;
+ fragmentOffset += fragmentLen;
+ } while (fragmentOffset < contentLen);
+
+ if (!fragmentWritten) {
+ /* Nothing was written if we got here, so the whole message must have
+ * been acknowledged. Discard it. */
+ SSL_TRC(10, ("%d: SSL3[%d]: FragmentHandshake %d: removed",
+ SSL_GETPID(), ss->fd, msgSeq));
+ PR_REMOVE_LINK(&msg->link);
+ dtls_FreeHandshakeMessage(msg);
+ }
+
+ return SECSuccess;
+}
+
/* Transmit a flight of handshake messages, stuffing them
- * into as few records as seems reasonable
+ * into as few records as seems reasonable.
+ *
+ * TODO: Space separate UDP packets out a little.
*
* Called from:
* dtls_FlushHandshake()
* dtls_RetransmitTimerExpiredCb()
*/
-static SECStatus
+SECStatus
dtls_TransmitMessageFlight(sslSocket *ss)
{
SECStatus rv = SECSuccess;
PRCList *msg_p;
- PRUint16 room_left = ss->ssl3.mtu;
- PRInt32 sent;
+
+ SSL_TRC(10, ("%d: SSL3[%d]: dtls_TransmitMessageFlight",
+ SSL_GETPID(), ss->fd));
ssl_GetXmitBufLock(ss);
ssl_GetSpecReadLock(ss);
- /* DTLS does not buffer its handshake messages in
- * ss->pendingBuf, but rather in the lastMessageFlight
- * structure. This is just a sanity check that
- * some programming error hasn't inadvertantly
- * stuffed something in ss->pendingBuf
+ /* DTLS does not buffer its handshake messages in ss->pendingBuf, but rather
+ * in the lastMessageFlight structure. This is just a sanity check that some
+ * programming error hasn't inadvertantly stuffed something in
+ * ss->pendingBuf. This function uses ss->pendingBuf temporarily and it
+ * needs to be empty to start.
*/
PORT_Assert(!ss->pendingBuf.len);
+
for (msg_p = PR_LIST_HEAD(&ss->ssl3.hs.lastMessageFlight);
- msg_p != &ss->ssl3.hs.lastMessageFlight;
- msg_p = PR_NEXT_LINK(msg_p)) {
+ msg_p != &ss->ssl3.hs.lastMessageFlight;) {
DTLSQueuedMessage *msg = (DTLSQueuedMessage *)msg_p;
- /* The logic here is:
- *
- * 1. If this is a message that will not fit into the remaining
- * space, then flush.
- * 2. If the message will now fit into the remaining space,
- * encrypt, buffer, and loop.
- * 3. If the message will not fit, then fragment.
- *
- * At the end of the function, flush.
- */
- if ((msg->len + SSL3_BUFFER_FUDGE) > room_left) {
- /* The message will not fit into the remaining space, so flush */
- rv = dtls_SendSavedWriteData(ss);
- if (rv != SECSuccess)
- break;
-
- room_left = ss->ssl3.mtu;
- }
+ /* Move the pointer forward so that the functions below are free to
+ * remove messages from the list. */
+ msg_p = PR_NEXT_LINK(msg_p);
- if ((msg->len + SSL3_BUFFER_FUDGE) <= room_left) {
- /* The message will fit, so encrypt and then continue with the
- * next packet */
- sent = ssl3_SendRecord(ss, msg->cwSpec, msg->type,
- msg->data, msg->len,
- ssl_SEND_FLAG_FORCE_INTO_BUFFER);
- if (sent != msg->len) {
- rv = SECFailure;
- if (sent != -1) {
- PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
- }
- break;
- }
+ /* Note: This function fragments messages so that each record is close
+ * to full. This produces fewer records, but it means that messages can
+ * be quite fragmented. Adding an extra flush here would push new
+ * messages into new records and reduce fragmentation. */
- room_left = ss->ssl3.mtu - ss->pendingBuf.len;
+ if (msg->type == content_handshake) {
+ rv = dtls_FragmentHandshake(ss, msg);
} else {
- /* The message will not fit, so fragment.
- *
- * XXX OK for now. Arrange to coalesce the last fragment
- * of this message with the next message if possible.
- * That would be more efficient.
- */
- PRUint32 fragment_offset = 0;
- unsigned char fragment[DTLS_MAX_MTU]; /* >= than largest
- * plausible MTU */
-
- /* Assert that we have already flushed */
- PORT_Assert(room_left == ss->ssl3.mtu);
-
- /* Case 3: We now need to fragment this message
- * DTLS only supports fragmenting handshaking messages */
- PORT_Assert(msg->type == content_handshake);
-
- /* The headers consume 12 bytes so the smalles possible
- * message (i.e., an empty one) is 12 bytes
- */
- PORT_Assert(msg->len >= 12);
-
- while ((fragment_offset + 12) < msg->len) {
- PRUint32 fragment_len;
- const unsigned char *content = msg->data + 12;
- PRUint32 content_len = msg->len - 12;
-
- /* The reason we use 8 here is that that's the length of
- * the new DTLS data that we add to the header */
- fragment_len = PR_MIN((PRUint32)room_left - (SSL3_BUFFER_FUDGE + 8),
- content_len - fragment_offset);
- PORT_Assert(fragment_len < DTLS_MAX_MTU - 12);
- /* Make totally sure that we are within the buffer.
- * Note that the only way that fragment len could get
- * adjusted here is if
- *
- * (a) we are in release mode so the PORT_Assert is compiled out
- * (b) either the MTU table is inconsistent with DTLS_MAX_MTU
- * or ss->ssl3.mtu has become corrupt.
- */
- fragment_len = PR_MIN(fragment_len, DTLS_MAX_MTU - 12);
-
- /* Construct an appropriate-sized fragment */
- /* Type, length, sequence */
- PORT_Memcpy(fragment, msg->data, 6);
-
- /* Offset */
- fragment[6] = (fragment_offset >> 16) & 0xff;
- fragment[7] = (fragment_offset >> 8) & 0xff;
- fragment[8] = (fragment_offset)&0xff;
-
- /* Fragment length */
- fragment[9] = (fragment_len >> 16) & 0xff;
- fragment[10] = (fragment_len >> 8) & 0xff;
- fragment[11] = (fragment_len)&0xff;
-
- PORT_Memcpy(fragment + 12, content + fragment_offset,
- fragment_len);
-
- /*
- * Send the record. We do this in two stages
- * 1. Encrypt
- */
- sent = ssl3_SendRecord(ss, msg->cwSpec, msg->type,
- fragment, fragment_len + 12,
- ssl_SEND_FLAG_FORCE_INTO_BUFFER);
- if (sent != (fragment_len + 12)) {
- rv = SECFailure;
- if (sent != -1) {
- PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
- }
- break;
- }
-
- /* 2. Flush */
- rv = dtls_SendSavedWriteData(ss);
- if (rv != SECSuccess)
- break;
-
- fragment_offset += fragment_len;
- }
+ PORT_Assert(!tls13_MaybeTls13(ss));
+ rv = dtls_SendFragment(ss, msg, msg->data, msg->len);
+ }
+ if (rv != SECSuccess) {
+ break;
}
}
- /* Finally, we need to flush */
- if (rv == SECSuccess)
+ /* Finally, flush any data that wasn't flushed already. */
+ if (rv == SECSuccess) {
rv = dtls_SendSavedWriteData(ss);
+ }
/* Give up the locks */
ssl_ReleaseSpecReadLock(ss);
@@ -796,23 +898,59 @@ dtls_SendSavedWriteData(sslSocket *ss)
return SECSuccess;
}
-static SECStatus
-dtls_StartTimer(sslSocket *ss, PRUint32 time, DTLSTimerCb cb)
+void
+dtls_InitTimers(sslSocket *ss)
{
- PORT_Assert(ss->ssl3.hs.rtTimerCb == NULL);
+ unsigned int i;
+ dtlsTimer **timers[PR_ARRAY_SIZE(ss->ssl3.hs.timers)] = {
+ &ss->ssl3.hs.rtTimer,
+ &ss->ssl3.hs.ackTimer,
+ &ss->ssl3.hs.hdTimer
+ };
+ static const char *timerLabels[] = {
+ "retransmit", "ack", "holddown"
+ };
+
+ PORT_Assert(PR_ARRAY_SIZE(timers) == PR_ARRAY_SIZE(timerLabels));
+ for (i = 0; i < PR_ARRAY_SIZE(ss->ssl3.hs.timers); ++i) {
+ *timers[i] = &ss->ssl3.hs.timers[i];
+ ss->ssl3.hs.timers[i].label = timerLabels[i];
+ }
+}
- ss->ssl3.hs.rtRetries = 0;
- ss->ssl3.hs.rtTimerStarted = PR_IntervalNow();
- ss->ssl3.hs.rtTimeoutMs = time;
- ss->ssl3.hs.rtTimerCb = cb;
+SECStatus
+dtls_StartTimer(sslSocket *ss, dtlsTimer *timer, PRUint32 time, DTLSTimerCb cb)
+{
+ PORT_Assert(timer->cb == NULL);
+
+ SSL_TRC(10, ("%d: SSL3[%d]: %s dtls_StartTimer %s timeout=%d",
+ SSL_GETPID(), ss->fd, SSL_ROLE(ss), timer->label, time));
+
+ timer->started = PR_IntervalNow();
+ timer->timeout = time;
+ timer->cb = cb;
return SECSuccess;
}
+SECStatus
+dtls_RestartTimer(sslSocket *ss, dtlsTimer *timer)
+{
+ timer->started = PR_IntervalNow();
+ return SECSuccess;
+}
+
+PRBool
+dtls_TimerActive(sslSocket *ss, dtlsTimer *timer)
+{
+ return timer->cb != NULL;
+}
/* Start a timer for retransmission. */
static SECStatus
dtls_StartRetransmitTimer(sslSocket *ss)
{
- return dtls_StartTimer(ss, DTLS_RETRANSMIT_INITIAL_MS,
+ ss->ssl3.hs.rtRetries = 0;
+ return dtls_StartTimer(ss, ss->ssl3.hs.rtTimer,
+ DTLS_RETRANSMIT_INITIAL_MS,
dtls_RetransmitTimerExpiredCb);
}
@@ -820,7 +958,9 @@ dtls_StartRetransmitTimer(sslSocket *ss)
SECStatus
dtls_StartHolddownTimer(sslSocket *ss)
{
- return dtls_StartTimer(ss, DTLS_RETRANSMIT_FINISHED_MS,
+ ss->ssl3.hs.rtRetries = 0;
+ return dtls_StartTimer(ss, ss->ssl3.hs.rtTimer,
+ DTLS_RETRANSMIT_FINISHED_MS,
dtls_FinishedTimerCb);
}
@@ -831,11 +971,25 @@ dtls_StartHolddownTimer(sslSocket *ss)
* dtls_CheckTimer()
*/
void
-dtls_CancelTimer(sslSocket *ss)
+dtls_CancelTimer(sslSocket *ss, dtlsTimer *timer)
{
+ SSL_TRC(30, ("%d: SSL3[%d]: %s dtls_CancelTimer %s",
+ SSL_GETPID(), ss->fd, SSL_ROLE(ss),
+ timer->label));
+
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
- ss->ssl3.hs.rtTimerCb = NULL;
+ timer->cb = NULL;
+}
+
+static void
+dtls_CancelAllTimers(sslSocket *ss)
+{
+ unsigned int i;
+
+ for (i = 0; i < PR_ARRAY_SIZE(ss->ssl3.hs.timers); ++i) {
+ dtls_CancelTimer(ss, &ss->ssl3.hs.timers[i]);
+ }
}
/* Check the pending timer and fire the callback if it expired
@@ -845,22 +999,33 @@ dtls_CancelTimer(sslSocket *ss)
void
dtls_CheckTimer(sslSocket *ss)
{
+ unsigned int i;
+ SSL_TRC(30, ("%d: SSL3[%d]: dtls_CheckTimer (%s)",
+ SSL_GETPID(), ss->fd, ss->sec.isServer ? "server" : "client"));
+
ssl_GetSSL3HandshakeLock(ss);
- if (!ss->ssl3.hs.rtTimerCb) {
- ssl_ReleaseSSL3HandshakeLock(ss);
- return;
- }
- if ((PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted) >
- PR_MillisecondsToInterval(ss->ssl3.hs.rtTimeoutMs)) {
- /* Timer has expired */
- DTLSTimerCb cb = ss->ssl3.hs.rtTimerCb;
+ for (i = 0; i < PR_ARRAY_SIZE(ss->ssl3.hs.timers); ++i) {
+ dtlsTimer *timer = &ss->ssl3.hs.timers[i];
+ if (!timer->cb) {
+ continue;
+ }
+
+ if ((PR_IntervalNow() - timer->started) >=
+ PR_MillisecondsToInterval(timer->timeout)) {
+ /* Timer has expired */
+ DTLSTimerCb cb = timer->cb;
+
+ SSL_TRC(10, ("%d: SSL3[%d]: %s firing timer %s",
+ SSL_GETPID(), ss->fd, SSL_ROLE(ss),
+ timer->label));
- /* Cancel the timer so that we can call the CB safely */
- dtls_CancelTimer(ss);
+ /* Cancel the timer so that we can call the CB safely */
+ dtls_CancelTimer(ss, timer);
- /* Now call the CB */
- cb(ss);
+ /* Now call the CB */
+ cb(ss);
+ }
}
ssl_ReleaseSSL3HandshakeLock(ss);
}
@@ -874,9 +1039,6 @@ static void
dtls_FinishedTimerCb(sslSocket *ss)
{
dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
- if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
- ssl3_DestroyCipherSpec(ss->ssl3.pwSpec, PR_FALSE);
- }
}
/* Cancel the Finished hold-down timer and destroy the
@@ -895,8 +1057,8 @@ dtls_RehandshakeCleanup(sslSocket *ss)
return;
}
PORT_Assert((ss->version < SSL_LIBRARY_VERSION_TLS_1_3));
- dtls_CancelTimer(ss);
- ssl3_DestroyCipherSpec(ss->ssl3.pwSpec, PR_FALSE);
+ dtls_CancelAllTimers(ss);
+ dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
ss->ssl3.hs.sendMessageSeq = 0;
ss->ssl3.hs.recvMessageSeq = 0;
}
@@ -959,6 +1121,8 @@ dtls_HandleHelloVerifyRequest(sslSocket *ss, PRUint8 *b, PRUint32 length)
goto alert_loser;
}
+ dtls_ReceivedFirstMessageInFlight(ss);
+
/* The version.
*
* RFC 4347 required that you verify that the server versions
@@ -1103,27 +1267,53 @@ SECStatus
DTLS_GetHandshakeTimeout(PRFileDesc *socket, PRIntervalTime *timeout)
{
sslSocket *ss = NULL;
- PRIntervalTime elapsed;
- PRIntervalTime desired;
+ PRBool found = PR_FALSE;
+ PRIntervalTime now = PR_IntervalNow();
+ PRIntervalTime to;
+ unsigned int i;
+
+ *timeout = PR_INTERVAL_NO_TIMEOUT;
ss = ssl_FindSocket(socket);
- if (!ss)
+ if (!ss) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
+ }
- if (!IS_DTLS(ss))
+ if (!IS_DTLS(ss)) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
+ }
- if (!ss->ssl3.hs.rtTimerCb)
- return SECFailure;
+ for (i = 0; i < PR_ARRAY_SIZE(ss->ssl3.hs.timers); ++i) {
+ PRIntervalTime elapsed;
+ PRIntervalTime desired;
+ dtlsTimer *timer = &ss->ssl3.hs.timers[i];
- elapsed = PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted;
- desired = PR_MillisecondsToInterval(ss->ssl3.hs.rtTimeoutMs);
- if (elapsed > desired) {
- /* Timer expired */
- *timeout = PR_INTERVAL_NO_WAIT;
- } else {
- *timeout = desired - elapsed;
+ if (!timer->cb) {
+ continue;
+ }
+ found = PR_TRUE;
+
+ elapsed = now - timer->started;
+ desired = PR_MillisecondsToInterval(timer->timeout);
+ if (elapsed > desired) {
+ /* Timer expired */
+ *timeout = PR_INTERVAL_NO_WAIT;
+ return SECSuccess;
+ } else {
+ to = desired - elapsed;
+ }
+
+ if (*timeout > to) {
+ *timeout = to;
+ }
+ }
+
+ if (!found) {
+ PORT_SetError(SSL_ERROR_NO_TIMERS_FOUND);
+ return SECFailure;
}
return SECSuccess;
@@ -1137,72 +1327,50 @@ DTLS_GetHandshakeTimeout(PRFileDesc *socket, PRIntervalTime *timeout)
* seems like a good tradeoff for implementation effort and is
* consistent with the guidance of RFC 6347 Sections 4.1 and 4.2.4.1.
*
- * If the packet is not relevant, this function returns PR_FALSE.
- * If the packet is relevant, this function returns PR_TRUE
- * and sets |*seqNum| to the packet sequence number.
+ * If the packet is not relevant, this function returns PR_FALSE. If the packet
+ * is relevant, this function returns PR_TRUE and sets |*seqNumOut| to the
+ * packet sequence number (removing the epoch).
*/
PRBool
-dtls_IsRelevant(sslSocket *ss, const SSL3Ciphertext *cText,
- PRBool *sameEpoch, PRUint64 *seqNum)
+dtls_IsRelevant(sslSocket *ss, const ssl3CipherSpec *spec,
+ const SSL3Ciphertext *cText,
+ sslSequenceNumber *seqNumOut)
{
- const ssl3CipherSpec *crSpec = ss->ssl3.crSpec;
- DTLSEpoch epoch;
- sslSequenceNumber dtls_seq_num;
-
- epoch = cText->seq_num >> 48;
- *sameEpoch = crSpec->epoch == epoch;
- if (!*sameEpoch) {
- SSL_DBG(("%d: SSL3[%d]: dtls_IsRelevant, received packet "
- "from irrelevant epoch %d",
- SSL_GETPID(), ss->fd, epoch));
- return PR_FALSE;
- }
-
- dtls_seq_num = cText->seq_num & RECORD_SEQ_MAX;
- if (dtls_RecordGetRecvd(&crSpec->recvdRecords, dtls_seq_num) != 0) {
- SSL_DBG(("%d: SSL3[%d]: dtls_IsRelevant, rejecting "
- "potentially replayed packet",
- SSL_GETPID(), ss->fd));
+ sslSequenceNumber seqNum = cText->seq_num & RECORD_SEQ_MASK;
+ if (dtls_RecordGetRecvd(&spec->recvdRecords, seqNum) != 0) {
+ SSL_TRC(10, ("%d: SSL3[%d]: dtls_IsRelevant, rejecting "
+ "potentially replayed packet",
+ SSL_GETPID(), ss->fd));
return PR_FALSE;
}
- *seqNum = dtls_seq_num;
+ *seqNumOut = seqNum;
return PR_TRUE;
}
-/* In TLS 1.3, a client that receives a retransmission of the server's first
- * flight will reject that message and discard it (see dtls_IsRelevant() above).
- * However, we need to trigger retransmission to prevent loss of the client's
- * last flight from causing the connection to fail.
- *
- * This only triggers for a retransmitted ServerHello. Other (encrypted)
- * handshake messages do not trigger retransmission, so we are a little more
- * exposed to loss than is ideal.
- *
- * Note: This isn't an issue in earlier versions because the second-to-last
- * flight (sent by the server) includes the Finished message, which is not
- * dropped because it has the same epoch that the client currently expects.
- */
-SECStatus
-dtls_MaybeRetransmitHandshake(sslSocket *ss, const SSL3Ciphertext *cText,
- PRBool sameEpoch)
+void
+dtls_ReceivedFirstMessageInFlight(sslSocket *ss)
{
- SECStatus rv = SECSuccess;
- DTLSEpoch messageEpoch = cText->seq_num >> 48;
-
- /* Drop messages from other epochs if we are ignoring things. */
- if (!sameEpoch && ss->ssl3.hs.zeroRttIgnore != ssl_0rtt_ignore_none) {
- return SECSuccess;
- }
+ if (!IS_DTLS(ss))
+ return;
- if (!ss->sec.isServer && ss->version >= SSL_LIBRARY_VERSION_TLS_1_3 &&
- messageEpoch == 0 && cText->type == content_handshake) {
- ssl_GetSSL3HandshakeLock(ss);
- if (ss->ssl3.hs.rtTimerCb == dtls_FinishedTimerCb &&
- ss->ssl3.hs.ws == idle_handshake) {
- rv = dtls_RetransmitDetected(ss);
+ /* At this point we are advancing our state machine, so we can free our last
+ * flight of messages. */
+ if (ss->ssl3.hs.ws != idle_handshake ||
+ ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ /* We need to keep our last flight around in DTLS 1.2 and below,
+ * so we can retransmit it in response to other people's
+ * retransmits. */
+ dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
+
+ /* Reset the timer to the initial value if the retry counter
+ * is 0, per RFC 6347, Sec. 4.2.4.1 */
+ dtls_CancelTimer(ss, ss->ssl3.hs.rtTimer);
+ if (ss->ssl3.hs.rtRetries == 0) {
+ ss->ssl3.hs.rtTimer->timeout = DTLS_RETRANSMIT_INITIAL_MS;
}
- ssl_ReleaseSSL3HandshakeLock(ss);
}
- return rv;
+
+ /* Empty the ACK queue (TLS 1.3 only). */
+ ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL);
}