summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/ssl/dtlscon.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/lib/ssl/dtlscon.c
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/lib/ssl/dtlscon.c')
-rw-r--r--security/nss/lib/ssl/dtlscon.c1474
1 files changed, 1474 insertions, 0 deletions
diff --git a/security/nss/lib/ssl/dtlscon.c b/security/nss/lib/ssl/dtlscon.c
new file mode 100644
index 0000000000..a4a7c998c4
--- /dev/null
+++ b/security/nss/lib/ssl/dtlscon.c
@@ -0,0 +1,1474 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * DTLS Protocol
+ */
+
+#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_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[] = {
+ 1500 - 28, /* Ethernet MTU */
+ 1280 - 28, /* IPv6 minimum MTU */
+ 576 - 28, /* Common assumption */
+ 256 - 28 /* We're in serious trouble now */
+};
+
+#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[] = {
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+ TLS_DHE_DSS_WITH_RC4_128_SHA,
+ TLS_ECDH_RSA_WITH_RC4_128_SHA,
+ TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
+ TLS_RSA_WITH_RC4_128_MD5,
+ TLS_RSA_WITH_RC4_128_SHA,
+ 0 /* End of list marker */
+};
+
+/* Map back and forth between TLS and DTLS versions in wire format.
+ * Mapping table is:
+ *
+ * TLS DTLS
+ * 1.1 (0302) 1.0 (feff)
+ * 1.2 (0303) 1.2 (fefd)
+ * 1.3 (0304) 1.3 (0304)
+ */
+SSL3ProtocolVersion
+dtls_TLSVersionToDTLSVersion(SSL3ProtocolVersion tlsv)
+{
+ if (tlsv == SSL_LIBRARY_VERSION_TLS_1_1) {
+ return SSL_LIBRARY_VERSION_DTLS_1_0_WIRE;
+ }
+ if (tlsv == SSL_LIBRARY_VERSION_TLS_1_2) {
+ return SSL_LIBRARY_VERSION_DTLS_1_2_WIRE;
+ }
+ if (tlsv == SSL_LIBRARY_VERSION_TLS_1_3) {
+ return SSL_LIBRARY_VERSION_DTLS_1_3_WIRE;
+ }
+
+ /* Anything else is an error, so return
+ * the invalid version 0xffff. */
+ return 0xffff;
+}
+
+/* Map known DTLS versions to known TLS versions.
+ * - Invalid versions (< 1.0) return a version of 0
+ * - Versions > known return a version one higher than we know of
+ * to accomodate a theoretically newer version */
+SSL3ProtocolVersion
+dtls_DTLSVersionToTLSVersion(SSL3ProtocolVersion dtlsv)
+{
+ if (MSB(dtlsv) == 0xff) {
+ return 0;
+ }
+
+ if (dtlsv == SSL_LIBRARY_VERSION_DTLS_1_0_WIRE) {
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+ }
+ /* Handle the skipped version of DTLS 1.1 by returning
+ * an error. */
+ if (dtlsv == ((~0x0101) & 0xffff)) {
+ return 0;
+ }
+ if (dtlsv == SSL_LIBRARY_VERSION_DTLS_1_2_WIRE) {
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+ }
+ if (dtlsv == SSL_LIBRARY_VERSION_DTLS_1_3_WIRE) {
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+ }
+
+ /* Return a fictional higher version than we know of */
+ return SSL_LIBRARY_VERSION_MAX_SUPPORTED + 1;
+}
+
+/* On this socket, Disable non-DTLS cipher suites in the argument's list */
+SECStatus
+ssl3_DisableNonDTLSSuites(sslSocket *ss)
+{
+ const ssl3CipherSuite *suite;
+
+ for (suite = nonDTLSSuites; *suite; ++suite) {
+ PORT_CheckSuccess(ssl3_CipherPrefSet(ss, *suite, PR_FALSE));
+ }
+ return SECSuccess;
+}
+
+/* Allocate a DTLSQueuedMessage.
+ *
+ * Called from dtls_QueueMessage()
+ */
+static DTLSQueuedMessage *
+dtls_AllocQueuedMessage(ssl3CipherSpec *cwSpec, SSLContentType ct,
+ const unsigned char *data, PRUint32 len)
+{
+ DTLSQueuedMessage *msg;
+
+ msg = PORT_ZNew(DTLSQueuedMessage);
+ if (!msg)
+ return NULL;
+
+ msg->data = PORT_Alloc(len);
+ if (!msg->data) {
+ PORT_Free(msg);
+ return NULL;
+ }
+ PORT_Memcpy(msg->data, data, len);
+
+ msg->len = len;
+ msg->cwSpec = cwSpec;
+ msg->type = ct;
+ /* Safe if we are < 1.3, since the refct is
+ * already very high. */
+ ssl_CipherSpecAddRef(cwSpec);
+
+ return msg;
+}
+
+/*
+ * Free a handshake message
+ *
+ * Called from dtls_FreeHandshakeMessages()
+ */
+void
+dtls_FreeHandshakeMessage(DTLSQueuedMessage *msg)
+{
+ if (!msg)
+ return;
+
+ /* Safe if we are < 1.3, since the refct is
+ * already very high. */
+ ssl_CipherSpecRelease(msg->cwSpec);
+ PORT_ZFree(msg->data, msg->len);
+ PORT_Free(msg);
+}
+
+/*
+ * Free a list of handshake messages
+ *
+ * Called from:
+ * dtls_HandleHandshake()
+ * ssl3_DestroySSL3Info()
+ */
+void
+dtls_FreeHandshakeMessages(PRCList *list)
+{
+ PRCList *cur_p;
+
+ while (!PR_CLIST_IS_EMPTY(list)) {
+ cur_p = PR_LIST_TAIL(list);
+ PR_REMOVE_LINK(cur_p);
+ dtls_FreeHandshakeMessage((DTLSQueuedMessage *)cur_p);
+ }
+}
+
+/* Called by dtls_HandleHandshake() and dtls_MaybeRetransmitHandshake() if a
+ * handshake message retransmission is detected. */
+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 (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() - 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, 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() - timer->started,
+ timer->timeout / 4));
+ }
+
+ } 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
+ * Note that we are not backing off the timer here.
+ * 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, ss->ssl3.hs.rtTimer);
+ rv = dtls_TransmitMessageFlight(ss);
+ if (rv == SECSuccess) {
+ rv = dtls_StartHolddownTimer(ss);
+ }
+
+ } else {
+ PORT_Assert(timer->cb == NULL);
+ /* ... and ignore it. */
+ }
+ return rv;
+}
+
+static SECStatus
+dtls_HandleHandshakeMessage(sslSocket *ss, PRUint8 *data, PRBool last)
+{
+ ss->ssl3.hs.recvdHighWater = -1;
+
+ return ssl3_HandleHandshakeMessage(ss, data, ss->ssl3.hs.msg_len,
+ last);
+}
+
+/* Called only from ssl3_HandleRecord, for each (deciphered) DTLS record.
+ * origBuf is the decrypted ssl record content and is expected to contain
+ * complete handshake records
+ * Caller must hold the handshake and RecvBuf locks.
+ *
+ * Note that this code uses msg_len for two purposes:
+ *
+ * (1) To pass the length to ssl3_HandleHandshakeMessage()
+ * (2) To carry the length of a message currently being reassembled
+ *
+ * However, unlike ssl3_HandleHandshake(), it is not used to carry
+ * the state of reassembly (i.e., whether one is in progress). That
+ * is carried in recvdHighWater and recvdFragments.
+ */
+#define OFFSET_BYTE(o) (o / 8)
+#define OFFSET_MASK(o) (1 << (o % 8))
+
+SECStatus
+dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum,
+ 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));
+
+ while (buf.len > 0) {
+ PRUint8 type;
+ PRUint32 message_length;
+ PRUint16 message_seq;
+ PRUint32 fragment_offset;
+ PRUint32 fragment_length;
+ PRUint32 offset;
+
+ if (buf.len < 12) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ rv = SECFailure;
+ goto loser;
+ }
+
+ /* Parse the header */
+ type = buf.buf[0];
+ message_length = (buf.buf[1] << 16) | (buf.buf[2] << 8) | buf.buf[3];
+ message_seq = (buf.buf[4] << 8) | buf.buf[5];
+ fragment_offset = (buf.buf[6] << 16) | (buf.buf[7] << 8) | buf.buf[8];
+ fragment_length = (buf.buf[9] << 16) | (buf.buf[10] << 8) | buf.buf[11];
+
+#define MAX_HANDSHAKE_MSG_LEN 0x1ffff /* 128k - 1 */
+ if (message_length > MAX_HANDSHAKE_MSG_LEN) {
+ (void)ssl3_DecodeError(ss);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ rv = SECFailure;
+ goto loser;
+ }
+#undef MAX_HANDSHAKE_MSG_LEN
+
+ buf.buf += 12;
+ buf.len -= 12;
+
+ /* This fragment must be complete */
+ if (buf.len < fragment_length) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ rv = SECFailure;
+ goto loser;
+ }
+
+ /* Sanity check the packet contents */
+ if ((fragment_length + fragment_offset) > message_length) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ rv = SECFailure;
+ 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;
+ ss->ssl3.hs.helloRetry = PR_TRUE;
+ }
+
+ /* There are three ways we could not be ready for this packet.
+ *
+ * 1. It's a partial next message.
+ * 2. It's a partial or complete message beyond the next
+ * 3. It's a message we've already seen
+ *
+ * If it's the complete next message we accept it right away.
+ * This is the common case for short messages
+ */
+ if ((message_seq == ss->ssl3.hs.recvMessageSeq) &&
+ (fragment_offset == 0) &&
+ (fragment_length == message_length)) {
+ /* Complete next message. Process immediately */
+ 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 != SECSuccess) {
+ 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);
+ goto loser;
+ } else if (message_seq > ss->ssl3.hs.recvMessageSeq) {
+ /* Case 2
+ *
+ * Ignore this message. This means we don't handle out of
+ * order complete messages that well, but we're still
+ * compliant and this probably does not happen often
+ *
+ * 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
+ */
+ /* Make room for the message */
+ if (ss->ssl3.hs.recvdHighWater == -1) {
+ PRUint32 map_length = OFFSET_BYTE(message_length) + 1;
+
+ rv = sslBuffer_Grow(&ss->ssl3.hs.msg_body, message_length);
+ if (rv != SECSuccess)
+ goto loser;
+ /* Make room for the fragment map */
+ rv = sslBuffer_Grow(&ss->ssl3.hs.recvdFragments,
+ map_length);
+ if (rv != SECSuccess)
+ 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 = (SSLHandshakeType)type;
+ ss->ssl3.hs.msg_len = message_length;
+ }
+
+ /* If we have a message length mismatch, abandon the reassembly
+ * in progress and hope that the next retransmit will give us
+ * something sane
+ */
+ if (message_length != ss->ssl3.hs.msg_len) {
+ ss->ssl3.hs.recvdHighWater = -1;
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ rv = SECFailure;
+ goto loser;
+ }
+
+ /* 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:
+ *
+ * - recvdHighWater contains the highest contiguous number of
+ * bytes received
+ * - recvdFragments contains a bitmask of packets received
+ * above recvdHighWater
+ *
+ * This avoids having to fill in the bitmask in the common
+ * case of adjacent fragments received in sequence
+ */
+ if (fragment_offset <= (unsigned int)ss->ssl3.hs.recvdHighWater) {
+ /* Either this is the adjacent fragment or an overlapping
+ * fragment */
+ if (end > ss->ssl3.hs.recvdHighWater) {
+ ss->ssl3.hs.recvdHighWater = end;
+ }
+ } else {
+ for (offset = fragment_offset; offset < end; offset++) {
+ ss->ssl3.hs.recvdFragments.buf[OFFSET_BYTE(offset)] |=
+ OFFSET_MASK(offset);
+ }
+ }
+
+ /* Now figure out the new high water mark if appropriate */
+ for (offset = ss->ssl3.hs.recvdHighWater;
+ offset < ss->ssl3.hs.msg_len; offset++) {
+ /* Note that this loop is not efficient, since it counts
+ * bit by bit. If we have a lot of out-of-order packets,
+ * we should optimize this */
+ if (ss->ssl3.hs.recvdFragments.buf[OFFSET_BYTE(offset)] &
+ OFFSET_MASK(offset)) {
+ ss->ssl3.hs.recvdHighWater++;
+ } else {
+ break;
+ }
+ }
+
+ /* If we have all the bytes, then we are good to go */
+ if (ss->ssl3.hs.recvdHighWater == ss->ssl3.hs.msg_len) {
+ rv = dtls_HandleHandshakeMessage(ss, ss->ssl3.hs.msg_body.buf,
+ buf.len == fragment_length);
+
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+ }
+ }
+
+ buf.buf += fragment_length;
+ buf.len -= fragment_length;
+ }
+
+ // This should never happen, but belt and suspenders.
+ if (rv != SECSuccess) {
+ 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. */
+ return rv;
+}
+
+/* Enqueue a message (either handshake or CCS)
+ *
+ * Called from:
+ * dtls_StageHandshakeMessage()
+ * ssl3_SendChangeCipherSpecs()
+ */
+SECStatus
+dtls_QueueMessage(sslSocket *ss, SSLContentType ct,
+ const PRUint8 *pIn, PRInt32 nIn)
+{
+ SECStatus rv = SECSuccess;
+ DTLSQueuedMessage *msg = NULL;
+ ssl3CipherSpec *spec;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ spec = ss->ssl3.cwSpec;
+ msg = dtls_AllocQueuedMessage(spec, ct, pIn, nIn);
+
+ if (!msg) {
+ PORT_SetError(SEC_ERROR_NO_MEMORY);
+ rv = SECFailure;
+ } else {
+ PR_APPEND_LINK(&msg->link, &ss->ssl3.hs.lastMessageFlight);
+ }
+
+ return rv;
+}
+
+/* Add DTLS handshake message to the pending queue
+ * Empty the sendBuf buffer.
+ * Always set sendBuf.len to 0, even when returning SECFailure.
+ *
+ * Called from:
+ * ssl3_AppendHandshakeHeader()
+ * dtls_FlushHandshake()
+ */
+SECStatus
+dtls_StageHandshakeMessage(sslSocket *ss)
+{
+ SECStatus rv = SECSuccess;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ /* This function is sometimes called when no data is actually to
+ * be staged, so just return SECSuccess. */
+ if (!ss->sec.ci.sendBuf.buf || !ss->sec.ci.sendBuf.len)
+ return rv;
+
+ rv = dtls_QueueMessage(ss, ssl_ct_handshake,
+ ss->sec.ci.sendBuf.buf, ss->sec.ci.sendBuf.len);
+
+ /* Whether we succeeded or failed, toss the old handshake data. */
+ ss->sec.ci.sendBuf.len = 0;
+ return rv;
+}
+
+/* Enqueue the handshake message in sendBuf (if any) and then
+ * transmit the resulting flight of handshake messages.
+ *
+ * Called from:
+ * ssl3_FlushHandshake()
+ */
+SECStatus
+dtls_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags)
+{
+ SECStatus rv = SECSuccess;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ rv = dtls_StageHandshakeMessage(ss);
+ if (rv != SECSuccess)
+ return rv;
+
+ if (!(flags & ssl_SEND_FLAG_FORCE_INTO_BUFFER)) {
+ rv = dtls_TransmitMessageFlight(ss);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+
+ if (!(flags & ssl_SEND_FLAG_NO_RETRANSMIT)) {
+ rv = dtls_StartRetransmitTimer(ss);
+ } else {
+ PORT_Assert(ss->version < SSL_LIBRARY_VERSION_TLS_1_3);
+ }
+ }
+
+ return rv;
+}
+
+/* The callback for when the retransmit timer expires
+ *
+ * Called from:
+ * dtls_CheckTimer()
+ * dtls_HandleHandshake()
+ */
+static void
+dtls_RetransmitTimerExpiredCb(sslSocket *ss)
+{
+ SECStatus rv;
+ dtlsTimer *timer = ss->ssl3.hs.rtTimer;
+ ss->ssl3.hs.rtRetries++;
+
+ if (!(ss->ssl3.hs.rtRetries % 3)) {
+ /* If one of the messages was potentially greater than > MTU,
+ * then downgrade. Do this every time we have retransmitted a
+ * message twice, per RFC 6347 Sec. 4.1.1 */
+ dtls_SetMTU(ss, ss->ssl3.hs.maxMessageSent - 1);
+ }
+
+ rv = dtls_TransmitMessageFlight(ss);
+ if (rv == SECSuccess) {
+ /* Re-arm the timer */
+ timer->timeout *= 2;
+ if (timer->timeout > DTLS_RETRANSMIT_MAX_MS) {
+ timer->timeout = DTLS_RETRANSMIT_MAX_MS;
+ }
+
+ 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, 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 == ssl_ct_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;
+
+ /* Limit further by the record size limit. Account for the header. */
+ fragmentLen = PR_MIN(fragmentLen,
+ msg->cwSpec->recordSizeLimit - DTLS_HS_HDR_LEN);
+
+ /* Reduce to the space remaining in the MTU. */
+ fragmentLen = PR_MIN(fragmentLen,
+ ss->ssl3.mtu - /* MTU estimate. */
+ ss->pendingBuf.len - /* Less any unsent records. */
+ DTLS_MAX_EXPANSION - /* Allow for expansion. */
+ DTLS_HS_HDR_LEN); /* And the 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->nextSeqNum);
+ 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.
+ *
+ * TODO: Space separate UDP packets out a little.
+ *
+ * Called from:
+ * dtls_FlushHandshake()
+ * dtls_RetransmitTimerExpiredCb()
+ */
+SECStatus
+dtls_TransmitMessageFlight(sslSocket *ss)
+{
+ SECStatus rv = SECSuccess;
+ PRCList *msg_p;
+
+ 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. 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;) {
+ DTLSQueuedMessage *msg = (DTLSQueuedMessage *)msg_p;
+
+ /* Move the pointer forward so that the functions below are free to
+ * remove messages from the list. */
+ msg_p = PR_NEXT_LINK(msg_p);
+
+ /* 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. */
+
+ if (msg->type == ssl_ct_handshake) {
+ rv = dtls_FragmentHandshake(ss, msg);
+ } else {
+ PORT_Assert(!tls13_MaybeTls13(ss));
+ rv = dtls_SendFragment(ss, msg, msg->data, msg->len);
+ }
+ if (rv != SECSuccess) {
+ break;
+ }
+ }
+
+ /* Finally, flush any data that wasn't flushed already. */
+ if (rv == SECSuccess) {
+ rv = dtls_SendSavedWriteData(ss);
+ }
+
+ /* Give up the locks */
+ ssl_ReleaseSpecReadLock(ss);
+ ssl_ReleaseXmitBufLock(ss);
+
+ return rv;
+}
+
+/* Flush the data in the pendingBuf and update the max message sent
+ * so we can adjust the MTU estimate if we need to.
+ * Wrapper for ssl_SendSavedWriteData.
+ *
+ * Called from dtls_TransmitMessageFlight()
+ */
+static SECStatus
+dtls_SendSavedWriteData(sslSocket *ss)
+{
+ PRInt32 sent;
+
+ sent = ssl_SendSavedWriteData(ss);
+ if (sent < 0)
+ return SECFailure;
+
+ /* We should always have complete writes b/c datagram sockets
+ * don't really block */
+ if (ss->pendingBuf.len > 0) {
+ ssl_MapLowLevelError(SSL_ERROR_SOCKET_WRITE_FAILURE);
+ return SECFailure;
+ }
+
+ /* Update the largest message sent so we can adjust the MTU
+ * estimate if necessary */
+ if (sent > ss->ssl3.hs.maxMessageSent)
+ ss->ssl3.hs.maxMessageSent = sent;
+
+ return SECSuccess;
+}
+
+void
+dtls_InitTimers(sslSocket *ss)
+{
+ 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];
+ }
+}
+
+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)
+{
+ ss->ssl3.hs.rtRetries = 0;
+ return dtls_StartTimer(ss, ss->ssl3.hs.rtTimer,
+ DTLS_RETRANSMIT_INITIAL_MS,
+ dtls_RetransmitTimerExpiredCb);
+}
+
+/* Start a timer for holding an old cipher spec. */
+SECStatus
+dtls_StartHolddownTimer(sslSocket *ss)
+{
+ ss->ssl3.hs.rtRetries = 0;
+ return dtls_StartTimer(ss, ss->ssl3.hs.rtTimer,
+ DTLS_RETRANSMIT_FINISHED_MS,
+ dtls_FinishedTimerCb);
+}
+
+/* Cancel a pending timer
+ *
+ * Called from:
+ * dtls_HandleHandshake()
+ * dtls_CheckTimer()
+ */
+void
+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));
+
+ 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
+ *
+ * Called from ssl3_GatherCompleteHandshake()
+ */
+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);
+
+ 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, timer);
+
+ /* Now call the CB */
+ cb(ss);
+ }
+ }
+ ssl_ReleaseSSL3HandshakeLock(ss);
+}
+
+/* The callback to fire when the holddown timer for the Finished
+ * message expires and we can delete it
+ *
+ * Called from dtls_CheckTimer()
+ */
+static void
+dtls_FinishedTimerCb(sslSocket *ss)
+{
+ dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
+}
+
+/* Cancel the Finished hold-down timer and destroy the
+ * pending cipher spec. Note that this means that
+ * successive rehandshakes will fail if the Finished is
+ * lost.
+ *
+ * XXX OK for now. Figure out how to handle the combination
+ * of Finished lost and rehandshake
+ */
+void
+dtls_RehandshakeCleanup(sslSocket *ss)
+{
+ /* Skip this if we are handling a second ClientHello. */
+ if (ss->ssl3.hs.helloRetry) {
+ return;
+ }
+ PORT_Assert((ss->version < SSL_LIBRARY_VERSION_TLS_1_3));
+ dtls_CancelAllTimers(ss);
+ dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
+ ss->ssl3.hs.sendMessageSeq = 0;
+ ss->ssl3.hs.recvMessageSeq = 0;
+}
+
+/* Set the MTU to the next step less than or equal to the
+ * advertised value. Also used to downgrade the MTU by
+ * doing dtls_SetMTU(ss, biggest packet set).
+ *
+ * Passing 0 means set this to the largest MTU known
+ * (effectively resetting the PMTU backoff value).
+ *
+ * Called by:
+ * ssl3_InitState()
+ * dtls_RetransmitTimerExpiredCb()
+ */
+void
+dtls_SetMTU(sslSocket *ss, PRUint16 advertised)
+{
+ int i;
+
+ if (advertised == 0) {
+ ss->ssl3.mtu = COMMON_MTU_VALUES[0];
+ SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
+ return;
+ }
+
+ for (i = 0; i < PR_ARRAY_SIZE(COMMON_MTU_VALUES); i++) {
+ if (COMMON_MTU_VALUES[i] <= advertised) {
+ ss->ssl3.mtu = COMMON_MTU_VALUES[i];
+ SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
+ return;
+ }
+ }
+
+ /* Fallback */
+ ss->ssl3.mtu = COMMON_MTU_VALUES[PR_ARRAY_SIZE(COMMON_MTU_VALUES) - 1];
+ SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
+}
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a
+ * DTLS hello_verify_request
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+SECStatus
+dtls_HandleHelloVerifyRequest(sslSocket *ss, PRUint8 *b, PRUint32 length)
+{
+ int errCode = SSL_ERROR_RX_MALFORMED_HELLO_VERIFY_REQUEST;
+ SECStatus rv;
+ SSL3ProtocolVersion temp;
+ SSL3AlertDescription desc = illegal_parameter;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle hello_verify_request handshake",
+ SSL_GETPID(), ss->fd));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ if (ss->ssl3.hs.ws != wait_server_hello) {
+ errCode = SSL_ERROR_RX_UNEXPECTED_HELLO_VERIFY_REQUEST;
+ desc = unexpected_message;
+ goto alert_loser;
+ }
+
+ dtls_ReceivedFirstMessageInFlight(ss);
+
+ /* The version.
+ *
+ * RFC 4347 required that you verify that the server versions
+ * match (Section 4.2.1) in the HelloVerifyRequest and the
+ * ServerHello.
+ *
+ * RFC 6347 suggests (SHOULD) that servers always use 1.0 in
+ * HelloVerifyRequest and allows the versions not to match,
+ * especially when 1.2 is being negotiated.
+ *
+ * Therefore we do not do anything to enforce a match, just
+ * read and check that this value is sane.
+ */
+ rv = ssl_ClientReadVersion(ss, &b, &length, &temp);
+ if (rv != SECSuccess) {
+ goto loser; /* alert has been sent */
+ }
+
+ /* Read the cookie.
+ * IMPORTANT: The value of ss->ssl3.hs.cookie is only valid while the
+ * HelloVerifyRequest message remains valid. */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &ss->ssl3.hs.cookie, 1, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* alert has been sent */
+ }
+ if (ss->ssl3.hs.cookie.len > DTLS_COOKIE_BYTES) {
+ desc = decode_error;
+ goto alert_loser; /* malformed. */
+ }
+
+ ssl_GetXmitBufLock(ss); /*******************************/
+
+ /* Now re-send the client hello */
+ rv = ssl3_SendClientHello(ss, client_hello_retransmit);
+
+ ssl_ReleaseXmitBufLock(ss); /*******************************/
+
+ if (rv == SECSuccess)
+ return rv;
+
+alert_loser:
+ (void)SSL3_SendAlert(ss, alert_fatal, desc);
+
+loser:
+ ssl_MapLowLevelError(errCode);
+ return SECFailure;
+}
+
+/* Initialize the DTLS anti-replay window
+ *
+ * Called from:
+ * ssl3_SetupPendingCipherSpec()
+ * ssl3_InitCipherSpec()
+ */
+void
+dtls_InitRecvdRecords(DTLSRecvdRecords *records)
+{
+ PORT_Memset(records->data, 0, sizeof(records->data));
+ records->left = 0;
+ records->right = DTLS_RECVD_RECORDS_WINDOW - 1;
+}
+
+/*
+ * Has this DTLS record been received? Return values are:
+ * -1 -- out of range to the left
+ * 0 -- not received yet
+ * 1 -- replay
+ *
+ * Called from: ssl3_HandleRecord()
+ */
+int
+dtls_RecordGetRecvd(const DTLSRecvdRecords *records, sslSequenceNumber seq)
+{
+ PRUint64 offset;
+
+ /* Out of range to the left */
+ if (seq < records->left) {
+ return -1;
+ }
+
+ /* Out of range to the right; since we advance the window on
+ * receipt, that means that this packet has not been received
+ * yet */
+ if (seq > records->right)
+ return 0;
+
+ offset = seq % DTLS_RECVD_RECORDS_WINDOW;
+
+ return !!(records->data[offset / 8] & (1 << (offset % 8)));
+}
+
+/* Update the DTLS anti-replay window
+ *
+ * Called from ssl3_HandleRecord()
+ */
+void
+dtls_RecordSetRecvd(DTLSRecvdRecords *records, sslSequenceNumber seq)
+{
+ PRUint64 offset;
+
+ if (seq < records->left)
+ return;
+
+ if (seq > records->right) {
+ sslSequenceNumber new_left;
+ sslSequenceNumber new_right;
+ sslSequenceNumber right;
+
+ /* Slide to the right; this is the tricky part
+ *
+ * 1. new_top is set to have room for seq, on the
+ * next byte boundary by setting the right 8
+ * bits of seq
+ * 2. new_left is set to compensate.
+ * 3. Zero all bits between top and new_top. Since
+ * this is a ring, this zeroes everything as-yet
+ * unseen. Because we always operate on byte
+ * boundaries, we can zero one byte at a time
+ */
+ new_right = seq | 0x07;
+ new_left = (new_right - DTLS_RECVD_RECORDS_WINDOW) + 1;
+
+ if (new_right > records->right + DTLS_RECVD_RECORDS_WINDOW) {
+ PORT_Memset(records->data, 0, sizeof(records->data));
+ } else {
+ for (right = records->right + 8; right <= new_right; right += 8) {
+ offset = right % DTLS_RECVD_RECORDS_WINDOW;
+ records->data[offset / 8] = 0;
+ }
+ }
+
+ records->right = new_right;
+ records->left = new_left;
+ }
+
+ offset = seq % DTLS_RECVD_RECORDS_WINDOW;
+
+ records->data[offset / 8] |= (1 << (offset % 8));
+}
+
+SECStatus
+DTLS_GetHandshakeTimeout(PRFileDesc *socket, PRIntervalTime *timeout)
+{
+ sslSocket *ss = NULL;
+ PRBool found = PR_FALSE;
+ PRIntervalTime now = PR_IntervalNow();
+ PRIntervalTime to;
+ unsigned int i;
+
+ *timeout = PR_INTERVAL_NO_TIMEOUT;
+
+ ss = ssl_FindSocket(socket);
+
+ if (!ss) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ if (!IS_DTLS(ss)) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ 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];
+
+ 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;
+}
+
+PRBool
+dtls_IsLongHeader(SSL3ProtocolVersion version, PRUint8 firstOctet)
+{
+#ifndef UNSAFE_FUZZER_MODE
+ return version < SSL_LIBRARY_VERSION_TLS_1_3 ||
+ firstOctet == ssl_ct_handshake ||
+ firstOctet == ssl_ct_ack ||
+ firstOctet == ssl_ct_alert;
+#else
+ return PR_TRUE;
+#endif
+}
+
+PRBool
+dtls_IsDtls13Ciphertext(SSL3ProtocolVersion version, PRUint8 firstOctet)
+{
+ // Allow no version in case we haven't negotiated one yet.
+ return (version == 0 || version >= SSL_LIBRARY_VERSION_TLS_1_3) &&
+ (firstOctet & 0xe0) == 0x20;
+}
+
+DTLSEpoch
+dtls_ReadEpoch(const ssl3CipherSpec *crSpec, const PRUint8 *hdr)
+{
+ DTLSEpoch epoch;
+ DTLSEpoch maxEpoch;
+ DTLSEpoch partial;
+
+ if (dtls_IsLongHeader(crSpec->version, hdr[0])) {
+ return ((DTLSEpoch)hdr[3] << 8) | hdr[4];
+ }
+
+ /* A lot of how we recover the epoch here will depend on how we plan to
+ * manage KeyUpdate. In the case that we decide to install a new read spec
+ * as a KeyUpdate is handled, crSpec will always be the highest epoch we can
+ * possibly receive. That makes this easier to manage.
+ */
+ if (dtls_IsDtls13Ciphertext(crSpec->version, hdr[0])) {
+ /* TODO(ekr@rtfm.com: do something with the two-bit epoch. */
+ /* Use crSpec->epoch, or crSpec->epoch - 1 if the last bit differs. */
+ return crSpec->epoch - ((hdr[0] ^ crSpec->epoch) & 0x3);
+ }
+
+ /* dtls_GatherData should ensure that this works. */
+ PORT_Assert(hdr[0] == ssl_ct_application_data);
+
+ /* This uses the same method as is used to recover the sequence number in
+ * dtls_ReadSequenceNumber, except that the maximum value is set to the
+ * current epoch. */
+ partial = hdr[1] >> 6;
+ maxEpoch = PR_MAX(crSpec->epoch, 3);
+ epoch = (maxEpoch & 0xfffc) | partial;
+ if (partial > (maxEpoch & 0x03)) {
+ epoch -= 4;
+ }
+ return epoch;
+}
+
+static sslSequenceNumber
+dtls_ReadSequenceNumber(const ssl3CipherSpec *spec, const PRUint8 *hdr)
+{
+ sslSequenceNumber cap;
+ sslSequenceNumber partial;
+ sslSequenceNumber seqNum;
+ sslSequenceNumber mask;
+
+ if (dtls_IsLongHeader(spec->version, hdr[0])) {
+ static const unsigned int seqNumOffset = 5; /* type, version, epoch */
+ static const unsigned int seqNumLength = 6;
+ sslReader r = SSL_READER(hdr + seqNumOffset, seqNumLength);
+ (void)sslRead_ReadNumber(&r, seqNumLength, &seqNum);
+ return seqNum;
+ }
+
+ /* Only the least significant bits of the sequence number is available here.
+ * This recovers the value based on the next expected sequence number.
+ *
+ * This works by determining the maximum possible sequence number, which is
+ * half the range of possible values above the expected next value (the
+ * expected next value is in |spec->seqNum|). Then, the last part of the
+ * sequence number is replaced. If that causes the value to exceed the
+ * maximum, subtract an entire range.
+ */
+ if (hdr[0] & 0x08) {
+ cap = spec->nextSeqNum + (1ULL << 15);
+ partial = (((sslSequenceNumber)hdr[1]) << 8) |
+ (sslSequenceNumber)hdr[2];
+ mask = (1ULL << 16) - 1;
+ } else {
+ cap = spec->nextSeqNum + (1ULL << 7);
+ partial = (sslSequenceNumber)hdr[1];
+ mask = (1ULL << 8) - 1;
+ }
+ seqNum = (cap & ~mask) | partial;
+ /* The second check prevents the value from underflowing if we get a large
+ * gap at the start of a connection, where this subtraction would cause the
+ * sequence number to wrap to near UINT64_MAX. */
+ if ((partial > (cap & mask)) && (seqNum > mask)) {
+ seqNum -= mask + 1;
+ }
+ return seqNum;
+}
+
+/*
+ * DTLS relevance checks:
+ * Note that this code currently ignores all out-of-epoch packets,
+ * which means we lose some in the case of rehandshake +
+ * loss/reordering. Since DTLS is explicitly unreliable, this
+ * 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 |*seqNumOut| to the
+ * packet sequence number (removing the epoch).
+ */
+PRBool
+dtls_IsRelevant(sslSocket *ss, const ssl3CipherSpec *spec,
+ const SSL3Ciphertext *cText,
+ sslSequenceNumber *seqNumOut)
+{
+ sslSequenceNumber seqNum = dtls_ReadSequenceNumber(spec, cText->hdr);
+ 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;
+ }
+
+ *seqNumOut = seqNum;
+ return PR_TRUE;
+}
+
+void
+dtls_ReceivedFirstMessageInFlight(sslSocket *ss)
+{
+ if (!IS_DTLS(ss))
+ return;
+
+ /* 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;
+ }
+ }
+
+ /* Empty the ACK queue (TLS 1.3 only). */
+ ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL);
+}