Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(422)

Unified Diff: net/third_party/nss/ssl/dtls1con.c

Issue 9764001: Add DTLS support to NSS, contributed by Eric Rescorla. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Update AUTHORS Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/third_party/nss/ssl/derive.c ('k') | net/third_party/nss/ssl/manifest.mn » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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,1163 @@
+/*
+ * 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);
+
+/* -28 adjusts for the IP/UDP header */
+static const PRUint16 COMMON_MTU_VALUES[] = {
+ 1500 - 28, /* Ethernet MTU */
+ 1280 - 28, /* IPv6 minimum MTU */
+ 576 - 28, /* Common assumption */
+ 256 - 28 /* We're in serious trouble now */
+};
+
+#define DTLS_COOKIE_BYTES 32
+
+/* 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;
+
+ for (suite = nonDTLSSuites; *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)
+{
+ /* XXX 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) {
+ /* Do not attempt to process rest of messages in this record */
+ break;
+ }
+ } 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
+ *
+ * XXX 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
+ * reassembly state:
+ *
+ * - recvdHighWater contains the highest contiguous number of
+ * bytes received
+ * - recvdFragments contains a bitmask of packets received
+ * above recvdHighWater
+ *
+ * This avoids having to fill in the bitmask in the common
+ * case of adjacent fragments received in sequence
+ */
+ if (fragment_offset <= 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
+ * bit by bit. If we have a lot of out-of-order packets,
+ * we should optimize this */
+ if (ss->ssl3.hs.recvdFragments.buf[OFFSET_BYTE(offset)] &
+ OFFSET_MASK(offset)) {
+ ss->ssl3.hs.recvdHighWater++;
+ } else {
+ break;
+ }
+ }
+
+ /* If we have all the bytes, then we are good to go */
+ if (ss->ssl3.hs.recvdHighWater == ss->ssl3.hs.msg_len) {
+ 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.buf += fragment_length;
+ buf.len -= fragment_length;
+ }
+
+ origBuf->len = 0; /* So ssl3_GatherAppDataRecord will keep looping. */
+
+ /* XXX OK for now. In future handle rv == SECWouldBlock safely in order
+ * to deal with asynchronous certificate 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)
+{
+ SECStatus rv = SECSuccess;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ rv = dtls_StageHandshakeMessage(ss);
+ if (rv != SECSuccess)
+ return rv;
+
+ if (!(flags & ssl_SEND_FLAG_FORCE_INTO_BUFFER)) {
+ rv = dtls_TransmitMessageFlight(ss);
+ if (rv != SECSuccess)
+ return rv;
+
+ if (!(flags & ssl_SEND_FLAG_NO_RETRANSMIT)) {
+ 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) {
+ /* XXX 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.
+ *
+ * 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(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->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.
+ *
+ * XXX 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);
+ ss->ssl3.hs.sendMessageSeq = 0;
+}
+
+/* Set the MTU to the next step less than or equal to the
+ * advertised value. Also used to downgrade the MTU by
+ * doing dtls_SetMTU(ss, biggest packet set).
+ *
+ * Passing 0 means set this to the largest MTU known
+ * (effectively resetting the PMTU backoff value).
+ *
+ * Called by:
+ * ssl3_InitState()
+ * dtls_RetransmitTimerExpiredCb()
+ */
+void
+dtls_SetMTU(sslSocket *ss, PRUint16 advertised)
+{
+ int i;
+
+ if (advertised == 0) {
+ ss->ssl3.mtu = COMMON_MTU_VALUES[0];
+ SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
+ return;
+ }
+
+ for (i = 0; i < PR_ARRAY_SIZE(COMMON_MTU_VALUES); i++) {
+ if (COMMON_MTU_VALUES[i] <= advertised) {
+ ss->ssl3.mtu = COMMON_MTU_VALUES[i];
+ SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
+ return;
+ }
+ }
+
+ /* Fallback */
+ ss->ssl3.mtu = COMMON_MTU_VALUES[PR_ARRAY_SIZE(COMMON_MTU_VALUES)-1];
+ SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
+}
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a
+ * DTLS hello_verify_request
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+SECStatus
+dtls_HandleHelloVerifyRequest(sslSocket *ss, 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 Section 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
« no previous file with comments | « net/third_party/nss/ssl/derive.c ('k') | net/third_party/nss/ssl/manifest.mn » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698