diff options
Diffstat (limited to 'security/nss/lib/ssl/dtlscon.c')
-rw-r--r-- | security/nss/lib/ssl/dtlscon.c | 720 |
1 files changed, 276 insertions, 444 deletions
diff --git a/security/nss/lib/ssl/dtlscon.c b/security/nss/lib/ssl/dtlscon.c index 2f335f924..fbd1779db 100644 --- a/security/nss/lib/ssl/dtlscon.c +++ b/security/nss/lib/ssl/dtlscon.c @@ -10,17 +10,16 @@ #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[] = { @@ -31,9 +30,6 @@ 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[] = { @@ -123,9 +119,9 @@ static DTLSQueuedMessage * dtls_AllocQueuedMessage(ssl3CipherSpec *cwSpec, SSL3ContentType type, const unsigned char *data, PRUint32 len) { - DTLSQueuedMessage *msg; + DTLSQueuedMessage *msg = NULL; - msg = PORT_ZNew(DTLSQueuedMessage); + msg = PORT_ZAlloc(sizeof(DTLSQueuedMessage)); if (!msg) return NULL; @@ -141,7 +137,7 @@ dtls_AllocQueuedMessage(ssl3CipherSpec *cwSpec, SSL3ContentType type, msg->type = type; /* Safe if we are < 1.3, since the refct is * already very high. */ - ssl_CipherSpecAddRef(cwSpec); + tls13_CipherSpecAddRef(cwSpec); return msg; } @@ -159,7 +155,7 @@ dtls_FreeHandshakeMessage(DTLSQueuedMessage *msg) /* Safe if we are < 1.3, since the refct is * already very high. */ - ssl_CipherSpecRelease(msg->cwSpec); + tls13_CipherSpecRelease(msg->cwSpec); PORT_ZFree(msg->data, msg->len); PORT_Free(msg); } @@ -188,38 +184,37 @@ 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 (timer->cb == dtls_RetransmitTimerExpiredCb) { + if (ss->ssl3.hs.rtTimerCb == 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)) { + if ((PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted) > + (ss->ssl3.hs.rtTimeoutMs / 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_CancelTimer(ss); 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)); + PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted, + ss->ssl3.hs.rtTimeoutMs / 4)); } - } else if (timer->cb == dtls_FinishedTimerCb) { + } else if (ss->ssl3.hs.rtTimerCb == dtls_FinishedTimerCb) { SSL_TRC(30, ("%d: SSL3[%d]: Retransmit detected in holddown", SSL_GETPID(), ss->fd)); /* Retransmit the messages and re-arm the timer @@ -227,14 +222,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, ss->ssl3.hs.rtTimer); + dtls_CancelTimer(ss); rv = dtls_TransmitMessageFlight(ss); if (rv == SECSuccess) { rv = dtls_StartHolddownTimer(ss); } } else { - PORT_Assert(timer->cb == NULL); + PORT_Assert(ss->ssl3.hs.rtTimerCb == NULL); /* ... and ignore it. */ } return rv; @@ -243,8 +238,19 @@ 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); } @@ -267,8 +273,7 @@ dtls_HandleHandshakeMessage(sslSocket *ss, PRUint8 *data, PRBool last) #define OFFSET_MASK(o) (1 << (o % 8)) SECStatus -dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, - sslBuffer *origBuf) +dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf) { /* XXX OK for now. * This doesn't work properly with asynchronous certificate validation. @@ -278,9 +283,6 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, */ 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)); @@ -296,7 +298,7 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, if (buf.len < 12) { PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE); rv = SECFailure; - goto loser; + break; } /* Parse the header */ @@ -321,28 +323,14 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, if (buf.len < fragment_length) { PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE); rv = SECFailure; - goto loser; + break; } /* 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; + break; } /* There are three ways we could not be ready for this packet. @@ -358,20 +346,20 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, (fragment_offset == 0) && (fragment_length == message_length)) { /* Complete next message. Process immediately */ - ss->ssl3.hs.msg_type = (SSLHandshakeType)type; + ss->ssl3.hs.msg_type = (SSL3HandshakeType)type; ss->ssl3.hs.msg_len = message_length; rv = dtls_HandleHandshakeMessage(ss, buf.buf, buf.len == fragment_length); if (rv == SECFailure) { - goto loser; + break; /* Discard the remainder of the record. */ } } 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; + break; } else if (message_seq > ss->ssl3.hs.recvMessageSeq) { /* Case 2 * @@ -381,12 +369,7 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, * * 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 @@ -397,18 +380,18 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, rv = sslBuffer_Grow(&ss->ssl3.hs.msg_body, message_length); if (rv != SECSuccess) - goto loser; + break; /* Make room for the fragment map */ rv = sslBuffer_Grow(&ss->ssl3.hs.recvdFragments, map_length); if (rv != SECSuccess) - goto loser; + break; /* 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_type = (SSL3HandshakeType)type; ss->ssl3.hs.msg_len = message_length; } @@ -420,14 +403,14 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, ss->ssl3.hs.recvdHighWater = -1; PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE); rv = SECFailure; - goto loser; + break; } - /* 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); - } + /* 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); /* This logic is a bit tricky. We have two values for * reassembly state: @@ -443,11 +426,12 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, 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; - } + ss->ssl3.hs.recvdHighWater = fragment_offset + + fragment_length; } else { - for (offset = fragment_offset; offset < end; offset++) { + for (offset = fragment_offset; + offset < fragment_offset + fragment_length; + offset++) { ss->ssl3.hs.recvdFragments.buf[OFFSET_BYTE(offset)] |= OFFSET_MASK(offset); } @@ -473,7 +457,7 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, buf.len == fragment_length); if (rv == SECFailure) { - goto loser; + break; /* Discard the rest of the record. */ } } } @@ -483,26 +467,6 @@ dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum, 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 @@ -596,8 +560,6 @@ 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); } } @@ -614,7 +576,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)) { @@ -627,239 +589,175 @@ dtls_RetransmitTimerExpiredCb(sslSocket *ss) 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; + ss->ssl3.hs.rtTimeoutMs *= 2; + if (ss->ssl3.hs.rtTimeoutMs > DTLS_RETRANSMIT_MAX_MS) { + ss->ssl3.hs.rtTimeoutMs = DTLS_RETRANSMIT_MAX_MS; } - timer->started = PR_IntervalNow(); - timer->cb = dtls_RetransmitTimerExpiredCb; + ss->ssl3.hs.rtTimerStarted = PR_IntervalNow(); + ss->ssl3.hs.rtTimerCb = dtls_RetransmitTimerExpiredCb; SSL_TRC(30, ("%d: SSL3[%d]: Retransmit #%d, next in %d", SSL_GETPID(), ss->fd, - ss->ssl3.hs.rtRetries, timer->timeout)); + ss->ssl3.hs.rtRetries, ss->ssl3.hs.rtTimeoutMs)); } /* 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. - * - * TODO: Space separate UDP packets out a little. + * into as few records as seems reasonable * * Called from: * dtls_FlushHandshake() * dtls_RetransmitTimerExpiredCb() */ -SECStatus +static SECStatus dtls_TransmitMessageFlight(sslSocket *ss) { SECStatus rv = SECSuccess; PRCList *msg_p; - - SSL_TRC(10, ("%d: SSL3[%d]: dtls_TransmitMessageFlight", - SSL_GETPID(), ss->fd)); + PRUint16 room_left = ss->ssl3.mtu; + PRInt32 sent; 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. + /* 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 */ PORT_Assert(!ss->pendingBuf.len); - for (msg_p = PR_LIST_HEAD(&ss->ssl3.hs.lastMessageFlight); - msg_p != &ss->ssl3.hs.lastMessageFlight;) { + msg_p != &ss->ssl3.hs.lastMessageFlight; + msg_p = PR_NEXT_LINK(msg_p)) { 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); + /* 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; + } - /* 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->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; + } - if (msg->type == content_handshake) { - rv = dtls_FragmentHandshake(ss, msg); + room_left = ss->ssl3.mtu - ss->pendingBuf.len; } else { - PORT_Assert(!tls13_MaybeTls13(ss)); - rv = dtls_SendFragment(ss, msg, msg->data, msg->len); - } - if (rv != SECSuccess) { - break; + /* 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; + } } } - /* Finally, flush any data that wasn't flushed already. */ - if (rv == SECSuccess) { + /* Finally, we need to flush */ + if (rv == SECSuccess) rv = dtls_SendSavedWriteData(ss); - } /* Give up the locks */ ssl_ReleaseSpecReadLock(ss); @@ -898,59 +796,23 @@ dtls_SendSavedWriteData(sslSocket *ss) 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) +static SECStatus +dtls_StartTimer(sslSocket *ss, 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; -} + PORT_Assert(ss->ssl3.hs.rtTimerCb == NULL); -SECStatus -dtls_RestartTimer(sslSocket *ss, dtlsTimer *timer) -{ - timer->started = PR_IntervalNow(); + ss->ssl3.hs.rtRetries = 0; + ss->ssl3.hs.rtTimerStarted = PR_IntervalNow(); + ss->ssl3.hs.rtTimeoutMs = time; + ss->ssl3.hs.rtTimerCb = cb; 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, + return dtls_StartTimer(ss, DTLS_RETRANSMIT_INITIAL_MS, dtls_RetransmitTimerExpiredCb); } @@ -958,9 +820,7 @@ dtls_StartRetransmitTimer(sslSocket *ss) SECStatus dtls_StartHolddownTimer(sslSocket *ss) { - ss->ssl3.hs.rtRetries = 0; - return dtls_StartTimer(ss, ss->ssl3.hs.rtTimer, - DTLS_RETRANSMIT_FINISHED_MS, + return dtls_StartTimer(ss, DTLS_RETRANSMIT_FINISHED_MS, dtls_FinishedTimerCb); } @@ -971,25 +831,11 @@ dtls_StartHolddownTimer(sslSocket *ss) * dtls_CheckTimer() */ void -dtls_CancelTimer(sslSocket *ss, dtlsTimer *timer) +dtls_CancelTimer(sslSocket *ss) { - 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]); - } + ss->ssl3.hs.rtTimerCb = NULL; } /* Check the pending timer and fire the callback if it expired @@ -999,33 +845,22 @@ dtls_CancelAllTimers(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; + } - 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)); + if ((PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted) > + PR_MillisecondsToInterval(ss->ssl3.hs.rtTimeoutMs)) { + /* Timer has expired */ + DTLSTimerCb cb = ss->ssl3.hs.rtTimerCb; - /* Cancel the timer so that we can call the CB safely */ - dtls_CancelTimer(ss, timer); + /* Cancel the timer so that we can call the CB safely */ + dtls_CancelTimer(ss); - /* Now call the CB */ - cb(ss); - } + /* Now call the CB */ + cb(ss); } ssl_ReleaseSSL3HandshakeLock(ss); } @@ -1039,6 +874,9 @@ 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 @@ -1057,8 +895,8 @@ dtls_RehandshakeCleanup(sslSocket *ss) return; } PORT_Assert((ss->version < SSL_LIBRARY_VERSION_TLS_1_3)); - dtls_CancelAllTimers(ss); - dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight); + dtls_CancelTimer(ss); + ssl3_DestroyCipherSpec(ss->ssl3.pwSpec, PR_FALSE); ss->ssl3.hs.sendMessageSeq = 0; ss->ssl3.hs.recvMessageSeq = 0; } @@ -1121,8 +959,6 @@ 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 @@ -1267,53 +1103,27 @@ 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; + PRIntervalTime elapsed; + PRIntervalTime desired; ss = ssl_FindSocket(socket); - if (!ss) { - PORT_SetError(SEC_ERROR_INVALID_ARGS); + if (!ss) return SECFailure; - } - if (!IS_DTLS(ss)) { - PORT_SetError(SEC_ERROR_INVALID_ARGS); + if (!IS_DTLS(ss)) 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); + if (!ss->ssl3.hs.rtTimerCb) return SECFailure; + + 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; } return SECSuccess; @@ -1327,50 +1137,72 @@ 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 |*seqNumOut| to the - * packet sequence number (removing the epoch). + * 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. */ PRBool -dtls_IsRelevant(sslSocket *ss, const ssl3CipherSpec *spec, - const SSL3Ciphertext *cText, - sslSequenceNumber *seqNumOut) +dtls_IsRelevant(sslSocket *ss, const SSL3Ciphertext *cText, + PRBool *sameEpoch, PRUint64 *seqNum) { - 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)); + 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)); return PR_FALSE; } - *seqNumOut = seqNum; + *seqNum = dtls_seq_num; return PR_TRUE; } -void -dtls_ReceivedFirstMessageInFlight(sslSocket *ss) +/* 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) { - if (!IS_DTLS(ss)) - return; + SECStatus rv = SECSuccess; + DTLSEpoch messageEpoch = cText->seq_num >> 48; - /* 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; - } + /* Drop messages from other epochs if we are ignoring things. */ + if (!sameEpoch && ss->ssl3.hs.zeroRttIgnore != ssl_0rtt_ignore_none) { + return SECSuccess; } - /* Empty the ACK queue (TLS 1.3 only). */ - ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL); + 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); + } + ssl_ReleaseSSL3HandshakeLock(ss); + } + return rv; } |