Index: net/third_party/nss/ssl/dtls1con.c |
=================================================================== |
--- net/third_party/nss/ssl/dtls1con.c (revision 0) |
+++ net/third_party/nss/ssl/dtls1con.c (revision 0) |
@@ -0,0 +1,1169 @@ |
+/* |
+ * DTLS Protocol |
+ * |
+ * ***** BEGIN LICENSE BLOCK ***** |
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
+ * |
+ * The contents of this file are subject to the Mozilla Public License Version |
+ * 1.1 (the "License"); you may not use this file except in compliance with |
+ * the License. You may obtain a copy of the License at |
+ * http://www.mozilla.org/MPL/ |
+ * |
+ * Software distributed under the License is distributed on an "AS IS" basis, |
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
+ * for the specific language governing rights and limitations under the |
+ * License. |
+ * |
+ * The Original Code is the Netscape security libraries. |
+ * |
+ * The Initial Developer of the Original Code is |
+ * Netscape Communications Corporation. |
+ * Portions created by the Initial Developer are Copyright (C) 1994-2000 |
+ * the Initial Developer. All Rights Reserved. |
+ * |
+ * Contributor(s): |
+ * Eric Rescorla <ekr@rtfm.com> |
+ * |
+ * Alternatively, the contents of this file may be used under the terms of |
+ * either the GNU General Public License Version 2 or later (the "GPL"), or |
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
+ * in which case the provisions of the GPL or the LGPL are applicable instead |
+ * of those above. If you wish to allow use of your version of this file only |
+ * under the terms of either the GPL or the LGPL, and not to allow others to |
+ * use your version of this file under the terms of the MPL, indicate your |
+ * decision by deleting the provisions above and replace them with the notice |
+ * and other provisions required by the GPL or the LGPL. If you do not delete |
+ * the provisions above, a recipient may use your version of this file under |
+ * the terms of any one of the MPL, the GPL or the LGPL. |
+ * |
+ * ***** END LICENSE BLOCK ***** */ |
+/* $Id: $ */ |
+ |
+ |
+#include "ssl.h" |
+#include "sslimpl.h" |
+#include "sslproto.h" |
+ |
+#ifndef PR_ARRAY_SIZE |
+#define PR_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) |
+#endif |
+ |
+static SECStatus dtls_TransmitMessageFlight(sslSocket *ss); |
+static void dtls_RetransmitTimerExpiredCb(sslSocket *ss); |
+static SECStatus dtls_SendSavedWriteData(sslSocket *ss); |
+ |
+static const PRUint16 COMMON_MTU_VALUES[] = { /* -28 adjusts for the IP/UDP header */ |
+ 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 |
+ |
+ |
+/* List copied from ssl3con.c:cipherSuites */ |
+static const ssl3CipherSuite nonDtlsSuites[] = { |
+#ifdef NSS_ENABLE_ECC |
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, |
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA, |
+#endif /* NSS_ENABLE_ECC */ |
+ TLS_DHE_DSS_WITH_RC4_128_SHA, |
+#ifdef NSS_ENABLE_ECC |
+ TLS_ECDH_RSA_WITH_RC4_128_SHA, |
+ TLS_ECDH_ECDSA_WITH_RC4_128_SHA, |
+#endif /* NSS_ENABLE_ECC */ |
+ SSL_RSA_WITH_RC4_128_MD5, |
+ SSL_RSA_WITH_RC4_128_SHA, |
+ TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, |
+ SSL_RSA_EXPORT_WITH_RC4_40_MD5, |
+ 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) |
+*/ |
+SSL3ProtocolVersion |
+dtls_TLSVersionToDTLSVersion(SSL3ProtocolVersion tlsv) |
+{ |
+ /* Anything other than TLS 1.1 is an error, so return |
+ * the invalid version ffff. */ |
+ if (tlsv != SSL_LIBRARY_VERSION_TLS_1_1) |
+ return 0xffff; |
+ |
+ return SSL_LIBRARY_VERSION_DTLS_1_0_WIRE; |
+} |
+ |
+/* 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; |
+ |
+ /* Return a fictional higher version than we know of */ |
+ return SSL_LIBRARY_VERSION_TLS_1_1 + 1; |
+} |
+ |
+/* On this socket, Disable non-DTLS cipher suites in the argument's list */ |
+SECStatus |
+ssl3_DisableNonDTLSSuites(sslSocket * ss) |
+{ |
+ const ssl3CipherSuite * suite; |
+ |
+ suite = nonDtlsSuites; |
+ for (; *suite; ++suite) { |
+ SECStatus rv = ssl3_CipherPrefSet(ss, *suite, PR_FALSE); |
+ |
+ PORT_Assert(rv == SECSuccess); /* else is coding error */ |
+ } |
+ return SECSuccess; |
+} |
+ |
+/* Allocate a DTLSQueuedMessage. |
+ * |
+ * Called from dtls_QueueMessage() |
+ */ |
+static DTLSQueuedMessage * |
+dtls_AllocQueuedMessage(PRUint16 epoch, SSL3ContentType type, |
+ const unsigned char *data, PRUint32 len) |
+{ |
+ DTLSQueuedMessage *msg = NULL; |
+ |
+ msg = PORT_ZAlloc(sizeof(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->epoch = epoch; |
+ msg->type = type; |
+ |
+ return msg; |
+} |
+ |
+/* |
+ * Free a handshake message |
+ * |
+ * Called from dtls_FreeHandshakeMessages() |
+ */ |
+static void |
+dtls_FreeHandshakeMessage(DTLSQueuedMessage *msg) |
+{ |
+ if (!msg) |
+ return; |
+ |
+ 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 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, sslBuffer *origBuf) |
+{ |
+ /* TODO(ekr@rtfm.com): OK for now. |
+ * This doesn't work properly with asynchronous certificate validation. |
+ * because that returns a WOULDBLOCK error. The current DTLS |
+ * applications do not need asynchronous validation, but in the |
+ * future we will need to add this. |
+ */ |
+ sslBuffer buf = *origBuf; |
+ SECStatus rv = SECSuccess; |
+ |
+ 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; |
+ break; |
+ } |
+ |
+ /* 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_RECORD_TOO_LONG); |
+ return SECFailure; |
+ } |
+#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; |
+ break; |
+ } |
+ |
+ /* Sanity check the packet contents */ |
+ if ((fragment_length + fragment_offset) > message_length) { |
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE); |
+ rv = SECFailure; |
+ break; |
+ } |
+ |
+ /* 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 = (SSL3HandshakeType)type; |
+ ss->ssl3.hs.msg_len = message_length; |
+ |
+ /* 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; |
+ dtls_CancelTimer(ss); |
+ |
+ /* Reset the timer to the initial value if the retry counter |
+ is 0, per Sec. 4.2.4.1 */ |
+ if (ss->ssl3.hs.rtRetries == 0) { |
+ ss->ssl3.hs.rtTimeoutMs = INITIAL_DTLS_TIMEOUT_MS; |
+ } |
+ |
+ rv = ssl3_HandleHandshakeMessage(ss, buf.buf, ss->ssl3.hs.msg_len); |
+ if (rv == SECFailure) { |
+ break; /* Do not attempt to process rest of messages in this record */ |
+ } |
+ } else { |
+ if (message_seq < ss->ssl3.hs.recvMessageSeq) { |
+ /* Case 3: we do an immediate retransmit if we're |
+ in a waiting state*/ |
+ if (ss->ssl3.hs.rtTimerCb == NULL) { |
+ /* Ignore */ |
+ } |
+ else if (ss->ssl3.hs.rtTimerCb == |
+ dtls_RetransmitTimerExpiredCb) { |
+ SSL_TRC(30, ("%d: SSL3[%d]: Retransmit detected", |
+ SSL_GETPID(), ss->fd)); |
+ /* 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 should be |
+ */ |
+ 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); |
+ dtls_RetransmitTimerExpiredCb(ss); |
+ rv = SECSuccess; |
+ break; |
+ } |
+ else { |
+ SSL_TRC(30, |
+ ("%d: SSL3[%d]: We just retransmitted. Ignoring.", |
+ SSL_GETPID(), ss->fd)); |
+ rv = SECSuccess; |
+ break; |
+ } |
+ } |
+ else if (ss->ssl3.hs.rtTimerCb == dtls_FinishedTimerCb) { |
+ /* 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); |
+ rv = dtls_TransmitMessageFlight(ss); |
+ if (rv == SECSuccess) { |
+ rv = dtls_StartTimer(ss, dtls_FinishedTimerCb); |
+ } |
+ if (rv != SECSuccess) |
+ return rv; |
+ break; |
+ } |
+ |
+ } |
+ 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 |
+ |
+ TODO(ekr@rtfm.com): OK for now. Maybe do something smarter at some point? |
+ */ |
+ } else { |
+ /* 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) |
+ break; |
+ /* Make room for the fragment map */ |
+ rv = sslBuffer_Grow(&ss->ssl3.hs.recvdFragments, map_length); |
+ if (rv != SECSuccess) |
+ 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 = (SSL3HandshakeType)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; |
+ break; |
+ } |
+ |
+ /* 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 reconstruction 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 <= ss->ssl3.hs.recvdHighWater) { |
+ /* Either this is the adjacent fragment or an overlapping fragment */ |
+ ss->ssl3.hs.recvdHighWater = fragment_offset + fragment_length; |
+ } else { |
+ for (offset = fragment_offset; offset < (fragment_offset + fragment_length); 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 byte 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) { |
+ ss->ssl3.hs.recvdHighWater = -1; |
+ |
+ rv = ssl3_HandleHandshakeMessage(ss, ss->ssl3.hs.msg_body.buf, ss->ssl3.hs.msg_len); |
+ if (rv == SECFailure) |
+ break; /* Skip rest of record */ |
+ |
+ /* At this point we are advancing our state machine, so |
+ we can free our last flight of messages */ |
+ dtls_FreeHandshakeMessages(ss->ssl3.hs.lastMessageFlight); |
+ dtls_CancelTimer(ss); |
+ |
+ /* If there have been no retries this time, reset the |
+ timer value to the default per Section 4.2.4.1 */ |
+ if (ss->ssl3.hs.rtRetries == 0) { |
+ ss->ssl3.hs.rtTimeoutMs = INITIAL_DTLS_TIMEOUT_MS; |
+ } |
+ } |
+ } |
+ } |
+ |
+ buf.len -= fragment_length; |
+ buf.buf += fragment_length; |
+ } |
+ |
+ origBuf->len = 0; /* So ssl3_GatherAppDataRecord will keep looping. */ |
+ |
+ /* TODO(ekr@rtfm.com): OK for now. In future handle WOULDLOCK safely |
+ * in order to potentially deal with asynchronous verification */ |
+ return rv; |
+} |
+ |
+ |
+/* Enqueue a message (either handshake or CCS) |
+ * |
+ * Called from: |
+ * dtls_StageHandshakeMessage() |
+ * ssl3_SendChangeCipherSpecs() |
+ */ |
+SECStatus dtls_QueueMessage(sslSocket *ss, SSL3ContentType type, |
+ const SSL3Opaque *pIn, PRInt32 nIn) |
+{ |
+ SECStatus rv = SECSuccess; |
+ DTLSQueuedMessage *msg = NULL; |
+ |
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); |
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) ); |
+ |
+ msg = dtls_AllocQueuedMessage(ss->ssl3.cwSpec->epoch, type, 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. |
+ * This function returns SECSuccess or SECFailure, never SECWouldBlock. |
+ * 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, content_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) |
+{ |
+ PRInt32 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)) { |
+ ss->ssl3.hs.rtRetries = 0; |
+ rv = dtls_StartTimer(ss, dtls_RetransmitTimerExpiredCb); |
+ } |
+ } |
+ |
+ return rv; |
+} |
+ |
+/* The callback for when the retransmit timer expires |
+ * |
+ * Called from: |
+ * dtls_CheckTimer() |
+ * dtls_HandleHandshake() |
+ * |
+ */ |
+static void |
+dtls_RetransmitTimerExpiredCb(sslSocket *ss) |
+{ |
+ SECStatus rv = SECFailure; |
+ |
+ 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 */ |
+ rv = dtls_RestartTimer(ss, PR_TRUE, dtls_RetransmitTimerExpiredCb); |
+ } |
+ |
+ if (rv == SECFailure) { |
+ /* TODO(ekr@rtfm.com): OK for now. In future maybe signal the stack |
+ * that we couldn't transmit. For now, let the read handle any real |
+ * network errors */ |
+ } |
+} |
+ |
+ |
+/* Transmit a flight of handshake messages, stuffing them |
+ into as few records as seems reasonable |
+ |
+ Called from: |
+ dtls_FlushHandshake() |
+ dtls_RetransmitTimerExpiredCb() |
+ */ |
+static SECStatus |
+dtls_TransmitMessageFlight(sslSocket *ss) |
+{ |
+ SECStatus rv = SECSuccess; |
+ PRCList *msg_p; |
+ 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 |
+ */ |
+ 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)) { |
+ 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; |
+ } |
+ |
+ 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->epoch, msg->type, msg->data, |
+ msg->len, |
+ ssl_SEND_FLAG_FORCE_INTO_BUFFER | |
+ ssl_SEND_FLAG_USE_EPOCH); |
+ if (sent != msg->len) { |
+ rv = SECFailure; |
+ if (sent != -1) { |
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); |
+ } |
+ break; |
+ } |
+ |
+ room_left = ss->ssl3.mtu - ss->pendingBuf.len; |
+ } else { |
+ /* The message will not fit, so fragment. |
+ |
+ TODO(ekr@rtfm.com): 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((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 */ |
+ PORT_Memcpy(fragment, msg->data, 6); /* Type, length, sequence */ |
+ |
+ /* 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->epoch, msg->type, fragment, |
+ fragment_len + 12, ssl_SEND_FLAG_FORCE_INTO_BUFFER | |
+ ssl_SEND_FLAG_USE_EPOCH); |
+ 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, we need to flush */ |
+ 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; |
+} |
+ |
+/* Compress, MAC, encrypt a DTLS record. Allows specification of |
+ * the epoch using epoch value. If use_epoch is PR_TRUE then |
+ * we use the provided epoch. If use_epoch is PR_FALSE then |
+ * whatever the current value is in effect is used. |
+ * |
+ * Called from ssl3_SendRecord() |
+ */ |
+ |
+SECStatus |
+dtls_CompressMACEncryptRecord(sslSocket * ss, |
+ DTLSEpoch epoch, |
+ PRBool use_epoch, |
+ SSL3ContentType type, |
+ const SSL3Opaque * pIn, |
+ PRUint32 contentLen, |
+ sslBuffer * wrBuf) |
+{ |
+ SECStatus rv = SECFailure; |
+ ssl3CipherSpec * cwSpec; |
+ |
+ ssl_GetSpecReadLock(ss); /********************************/ |
+ |
+ /* The reason for this switch-hitting code is that we might have |
+ * a flight of records spanning an epoch boundary, e.g., |
+ * |
+ * ClientKeyExchange (epoch = 0) |
+ * ChangeCipherSpec (epoch = 0) |
+ * Finished (epoch = 1) |
+ * |
+ * Thus, each record needs a different cipher spec. The information |
+ * about which epoch to use is carried with the record. |
+ */ |
+ if (use_epoch) { |
+ if(ss->ssl3.cwSpec->epoch == epoch) |
+ cwSpec = ss->ssl3.cwSpec; |
+ else if (ss->ssl3.pwSpec->epoch == epoch) |
+ cwSpec = ss->ssl3.pwSpec; |
+ else |
+ cwSpec = NULL; |
+ } else { |
+ cwSpec = ss->ssl3.cwSpec; |
+ } |
+ |
+ if (cwSpec) { |
+ rv = ssl3_CompressMACEncryptRecord(cwSpec, |
+ ss->sec.isServer, |
+ PR_TRUE, |
+ type, pIn, contentLen, |
+ wrBuf); |
+ } else { |
+ PR_NOT_REACHED("Couldn't find a cipher spec matching epoch"); |
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); |
+ } |
+ ssl_ReleaseSpecReadLock(ss); /************************************/ |
+ |
+ return rv; |
+} |
+ |
+/* Start a timer |
+ * |
+ * Called from: |
+ * dtls_HandleHandshake() |
+ * dtls_FlushHAndshake() |
+ * dtls_RestartTimer() |
+ */ |
+ |
+SECStatus |
+dtls_StartTimer(sslSocket *ss, DtlsTimerCb cb) |
+{ |
+ PORT_Assert(ss->ssl3.hs.rtTimerCb == NULL); |
+ |
+ ss->ssl3.hs.rtTimerStarted = PR_IntervalNow(); |
+ ss->ssl3.hs.rtTimerCb = cb; |
+ |
+ return SECSuccess; |
+} |
+ |
+/* Restart a timer with optional backoff |
+ * |
+ * Called from dtls_RetransmitTimerExpiredCb() |
+ */ |
+ |
+SECStatus |
+dtls_RestartTimer(sslSocket *ss, PRBool backoff, DtlsTimerCb cb) |
+{ |
+ if (backoff) { |
+ ss->ssl3.hs.rtTimeoutMs *= 2; |
+ if(ss->ssl3.hs.rtTimeoutMs > MAX_DTLS_TIMEOUT_MS) |
+ ss->ssl3.hs.rtTimeoutMs = MAX_DTLS_TIMEOUT_MS; |
+ } |
+ |
+ return dtls_StartTimer(ss, cb); |
+} |
+ |
+/* Cancel a pending timer |
+ * |
+ * Called from: |
+ * dtls_HandleHandshake() |
+ * dtls_CheckTimer() |
+ */ |
+void |
+dtls_CancelTimer(sslSocket *ss) |
+{ |
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); |
+ |
+ ss->ssl3.hs.rtTimerCb = NULL; |
+} |
+ |
+ |
+/* Check the pending timer and fire the callback if it expired |
+ * |
+ * Called from ssl3_GatherCompleteHandshake() |
+ */ |
+void |
+dtls_CheckTimer(sslSocket *ss) |
+{ |
+ if (!ss->ssl3.hs.rtTimerCb) |
+ return; |
+ |
+ 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); |
+ |
+ /* Now call the CB */ |
+ cb(ss); |
+ } |
+} |
+ |
+/* The callback to fire when the holddown timer for the Finished |
+ * message expires and we can delete it |
+ * |
+ * Called from dtls_CheckTimer() |
+ */ |
+void |
+dtls_FinishedTimerCb(sslSocket *ss) |
+{ |
+ ssl3_DestroyCipherSpec(ss->ssl3.pwSpec, PR_FALSE); |
+} |
+ |
+/* 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. |
+ |
+ TODO(ekr@rtfm.com): OK for now. Figure out how to |
+ handle the combination of Finished lost and rehandshake |
+*/ |
+ |
+void |
+dtls_RehandshakeCleanup(sslSocket *ss) |
+{ |
+ dtls_CancelTimer(ss); |
+ ssl3_DestroyCipherSpec(ss->ssl3.pwSpec, PR_FALSE); |
+} |
+ |
+/* 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, SSL3Opaque *b, PRUint32 length) |
+{ |
+ int errCode = SSL_ERROR_RX_MALFORMED_HELLO_VERIFY_REQUEST; |
+ SECStatus rv; |
+ PRInt32 temp; |
+ SECItem cookie = {siBuffer, NULL, 0}; |
+ 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; |
+ } |
+ |
+ /* The version */ |
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length); |
+ if (temp < 0) { |
+ goto loser; /* alert has been sent */ |
+ } |
+ |
+ if (temp != SSL_LIBRARY_VERSION_DTLS_1_0_WIRE) { |
+ /* Note: this will need adjustment for DTLS 1.2 per S 4.2.1 */ |
+ |
+ goto alert_loser; |
+ } |
+ |
+ /* The cookie */ |
+ rv = ssl3_ConsumeHandshakeVariable(ss, &cookie, 1, &b, &length); |
+ if (rv != SECSuccess) { |
+ goto loser; /* alert has been sent */ |
+ } |
+ if (cookie.len > DTLS_COOKIE_BYTES) { |
+ desc = decode_error; |
+ goto alert_loser; /* malformed. */ |
+ } |
+ |
+ PORT_Memcpy(ss->ssl3.hs.cookie, cookie.data, cookie.len); |
+ ss->ssl3.hs.cookieLen = cookie.len; |
+ |
+ |
+ ssl_GetXmitBufLock(ss); /*******************************/ |
+ |
+ /* Now re-send the client hello */ |
+ rv = ssl3_SendClientHello(ss, PR_TRUE); |
+ |
+ ssl_ReleaseXmitBufLock(ss); /*******************************/ |
+ |
+ if (rv == SECSuccess) |
+ return rv; |
+ |
+alert_loser: |
+ (void)SSL3_SendAlert(ss, alert_fatal, desc); |
+ |
+loser: |
+ errCode = 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: dtls_HandleRecord() |
+ */ |
+int |
+dtls_RecordGetRecvd(DTLSRecvdRecords *records, PRUint64 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, PRUint64 seq) |
+{ |
+ PRUint64 offset; |
+ |
+ if (seq < records->left) |
+ return; |
+ |
+ if (seq > records->right) { |
+ PRUint64 new_left; |
+ PRUint64 new_right; |
+ PRUint64 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; |
+ |
+ 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_GetTimeout(PRFileDesc *socket, PRIntervalTime *timeout) |
+{ |
+ sslSocket * ss = NULL; |
+ PRIntervalTime elapsed; |
+ PRIntervalTime desired; |
+ |
+ ss = ssl_FindSocket(socket); |
+ |
+ if (!ss) |
+ return SECFailure; |
+ |
+ if (!IS_DTLS(ss)) |
+ return SECFailure; |
+ |
+ 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; |
+} |
+ |
Property changes on: net/third_party/nss/ssl/dtls1con.c |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |