diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-06-06 21:27:04 +0200 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-06-06 21:27:04 +0200 |
commit | 4a71b30364a4b6d1eaf16fcfdc8e873e6697f293 (patch) | |
tree | a47014077c14579249859ad34afcc5a8f2f0730a /security/nss/lib/ssl/dtlscon.c | |
parent | d7da72799521386c110dbba73b1e483b00a0a56a (diff) | |
parent | 2dad0ec41d0b69c0a815012e6ea4bdde81b2875b (diff) | |
download | UXP-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.c | 720 |
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); } |