mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-10-05 04:55:33 +00:00
1481 lines
47 KiB
C
1481 lines
47 KiB
C
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/*
|
|
* DTLS Protocol
|
|
*/
|
|
|
|
#include "ssl.h"
|
|
#include "sslimpl.h"
|
|
#include "sslproto.h"
|
|
#include "dtls13con.h"
|
|
|
|
#ifndef PR_ARRAY_SIZE
|
|
#define PR_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
|
#endif
|
|
|
|
static SECStatus dtls_StartRetransmitTimer(sslSocket *ss);
|
|
static void dtls_RetransmitTimerExpiredCb(sslSocket *ss);
|
|
static SECStatus dtls_SendSavedWriteData(sslSocket *ss);
|
|
static void dtls_FinishedTimerCb(sslSocket *ss);
|
|
static void dtls_CancelAllTimers(sslSocket *ss);
|
|
|
|
/* -28 adjusts for the IP/UDP header */
|
|
static const PRUint16 COMMON_MTU_VALUES[] = {
|
|
1500 - 28, /* Ethernet MTU */
|
|
1280 - 28, /* IPv6 minimum MTU */
|
|
576 - 28, /* Common assumption */
|
|
256 - 28 /* We're in serious trouble now */
|
|
};
|
|
|
|
#define DTLS_COOKIE_BYTES 32
|
|
/* Maximum DTLS expansion = header + IV + max CBC padding +
|
|
* maximum MAC. */
|
|
#define DTLS_MAX_EXPANSION (DTLS_RECORD_HEADER_LENGTH + 16 + 16 + 32)
|
|
|
|
/* List copied from ssl3con.c:cipherSuites */
|
|
static const ssl3CipherSuite nonDTLSSuites[] = {
|
|
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
|
TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
|
TLS_DHE_DSS_WITH_RC4_128_SHA,
|
|
TLS_ECDH_RSA_WITH_RC4_128_SHA,
|
|
TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
|
|
TLS_RSA_WITH_RC4_128_MD5,
|
|
TLS_RSA_WITH_RC4_128_SHA,
|
|
0 /* End of list marker */
|
|
};
|
|
|
|
/* Map back and forth between TLS and DTLS versions in wire format.
|
|
* Mapping table is:
|
|
*
|
|
* TLS DTLS
|
|
* 1.1 (0302) 1.0 (feff)
|
|
* 1.2 (0303) 1.2 (fefd)
|
|
* 1.3 (0304) 1.3 (fefc)
|
|
*/
|
|
SSL3ProtocolVersion
|
|
dtls_TLSVersionToDTLSVersion(SSL3ProtocolVersion tlsv)
|
|
{
|
|
if (tlsv == SSL_LIBRARY_VERSION_TLS_1_1) {
|
|
return SSL_LIBRARY_VERSION_DTLS_1_0_WIRE;
|
|
}
|
|
if (tlsv == SSL_LIBRARY_VERSION_TLS_1_2) {
|
|
return SSL_LIBRARY_VERSION_DTLS_1_2_WIRE;
|
|
}
|
|
if (tlsv == SSL_LIBRARY_VERSION_TLS_1_3) {
|
|
return SSL_LIBRARY_VERSION_DTLS_1_3_WIRE;
|
|
}
|
|
|
|
/* Anything other than TLS 1.1 or 1.2 is an error, so return
|
|
* the invalid version 0xffff. */
|
|
return 0xffff;
|
|
}
|
|
|
|
/* Map known DTLS versions to known TLS versions.
|
|
* - Invalid versions (< 1.0) return a version of 0
|
|
* - Versions > known return a version one higher than we know of
|
|
* to accomodate a theoretically newer version */
|
|
SSL3ProtocolVersion
|
|
dtls_DTLSVersionToTLSVersion(SSL3ProtocolVersion dtlsv)
|
|
{
|
|
if (MSB(dtlsv) == 0xff) {
|
|
return 0;
|
|
}
|
|
|
|
if (dtlsv == SSL_LIBRARY_VERSION_DTLS_1_0_WIRE) {
|
|
return SSL_LIBRARY_VERSION_TLS_1_1;
|
|
}
|
|
/* Handle the skipped version of DTLS 1.1 by returning
|
|
* an error. */
|
|
if (dtlsv == ((~0x0101) & 0xffff)) {
|
|
return 0;
|
|
}
|
|
if (dtlsv == SSL_LIBRARY_VERSION_DTLS_1_2_WIRE) {
|
|
return SSL_LIBRARY_VERSION_TLS_1_2;
|
|
}
|
|
if (dtlsv == SSL_LIBRARY_VERSION_DTLS_1_3_WIRE) {
|
|
return SSL_LIBRARY_VERSION_TLS_1_3;
|
|
}
|
|
|
|
/* Return a fictional higher version than we know of */
|
|
return SSL_LIBRARY_VERSION_MAX_SUPPORTED + 1;
|
|
}
|
|
|
|
/* On this socket, Disable non-DTLS cipher suites in the argument's list */
|
|
SECStatus
|
|
ssl3_DisableNonDTLSSuites(sslSocket *ss)
|
|
{
|
|
const ssl3CipherSuite *suite;
|
|
|
|
for (suite = nonDTLSSuites; *suite; ++suite) {
|
|
PORT_CheckSuccess(ssl3_CipherPrefSet(ss, *suite, PR_FALSE));
|
|
}
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* Allocate a DTLSQueuedMessage.
|
|
*
|
|
* Called from dtls_QueueMessage()
|
|
*/
|
|
static DTLSQueuedMessage *
|
|
dtls_AllocQueuedMessage(ssl3CipherSpec *cwSpec, SSLContentType ct,
|
|
const unsigned char *data, PRUint32 len)
|
|
{
|
|
DTLSQueuedMessage *msg;
|
|
|
|
msg = PORT_ZNew(DTLSQueuedMessage);
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
msg->data = PORT_Alloc(len);
|
|
if (!msg->data) {
|
|
PORT_Free(msg);
|
|
return NULL;
|
|
}
|
|
PORT_Memcpy(msg->data, data, len);
|
|
|
|
msg->len = len;
|
|
msg->cwSpec = cwSpec;
|
|
msg->type = ct;
|
|
/* Safe if we are < 1.3, since the refct is
|
|
* already very high. */
|
|
ssl_CipherSpecAddRef(cwSpec);
|
|
|
|
return msg;
|
|
}
|
|
|
|
/*
|
|
* Free a handshake message
|
|
*
|
|
* Called from dtls_FreeHandshakeMessages()
|
|
*/
|
|
void
|
|
dtls_FreeHandshakeMessage(DTLSQueuedMessage *msg)
|
|
{
|
|
if (!msg)
|
|
return;
|
|
|
|
/* Safe if we are < 1.3, since the refct is
|
|
* already very high. */
|
|
ssl_CipherSpecRelease(msg->cwSpec);
|
|
PORT_ZFree(msg->data, msg->len);
|
|
PORT_Free(msg);
|
|
}
|
|
|
|
/*
|
|
* Free a list of handshake messages
|
|
*
|
|
* Called from:
|
|
* dtls_HandleHandshake()
|
|
* ssl3_DestroySSL3Info()
|
|
*/
|
|
void
|
|
dtls_FreeHandshakeMessages(PRCList *list)
|
|
{
|
|
PRCList *cur_p;
|
|
|
|
while (!PR_CLIST_IS_EMPTY(list)) {
|
|
cur_p = PR_LIST_TAIL(list);
|
|
PR_REMOVE_LINK(cur_p);
|
|
dtls_FreeHandshakeMessage((DTLSQueuedMessage *)cur_p);
|
|
}
|
|
}
|
|
|
|
/* Called by dtls_HandleHandshake() and dtls_MaybeRetransmitHandshake() if a
|
|
* handshake message retransmission is detected. */
|
|
static SECStatus
|
|
dtls_RetransmitDetected(sslSocket *ss)
|
|
{
|
|
dtlsTimer *timer = ss->ssl3.hs.rtTimer;
|
|
SECStatus rv = SECSuccess;
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
|
|
if (timer->cb == dtls_RetransmitTimerExpiredCb) {
|
|
/* Check to see if we retransmitted recently. If so,
|
|
* suppress the triggered retransmit. This avoids
|
|
* retransmit wars after packet loss.
|
|
* This is not in RFC 5346 but it should be.
|
|
*/
|
|
if ((PR_IntervalNow() - timer->started) >
|
|
(timer->timeout / 4)) {
|
|
SSL_TRC(30,
|
|
("%d: SSL3[%d]: Shortcutting retransmit timer",
|
|
SSL_GETPID(), ss->fd));
|
|
|
|
/* Cancel the timer and call the CB,
|
|
* which re-arms the timer */
|
|
dtls_CancelTimer(ss, ss->ssl3.hs.rtTimer);
|
|
dtls_RetransmitTimerExpiredCb(ss);
|
|
} else {
|
|
SSL_TRC(30,
|
|
("%d: SSL3[%d]: Ignoring retransmission: "
|
|
"last retransmission %dms ago, suppressed for %dms",
|
|
SSL_GETPID(), ss->fd,
|
|
PR_IntervalNow() - timer->started,
|
|
timer->timeout / 4));
|
|
}
|
|
|
|
} else if (timer->cb == dtls_FinishedTimerCb) {
|
|
SSL_TRC(30, ("%d: SSL3[%d]: Retransmit detected in holddown",
|
|
SSL_GETPID(), ss->fd));
|
|
/* Retransmit the messages and re-arm the timer
|
|
* Note that we are not backing off the timer here.
|
|
* The spec isn't clear and my reasoning is that this
|
|
* may be a re-ordered packet rather than slowness,
|
|
* so let's be aggressive. */
|
|
dtls_CancelTimer(ss, ss->ssl3.hs.rtTimer);
|
|
rv = dtls_TransmitMessageFlight(ss);
|
|
if (rv == SECSuccess) {
|
|
rv = dtls_StartHolddownTimer(ss);
|
|
}
|
|
|
|
} else {
|
|
PORT_Assert(timer->cb == NULL);
|
|
/* ... and ignore it. */
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
static SECStatus
|
|
dtls_HandleHandshakeMessage(sslSocket *ss, PRUint8 *data, PRBool last)
|
|
{
|
|
ss->ssl3.hs.recvdHighWater = -1;
|
|
|
|
return ssl3_HandleHandshakeMessage(ss, data, ss->ssl3.hs.msg_len,
|
|
last);
|
|
}
|
|
|
|
/* Called only from ssl3_HandleRecord, for each (deciphered) DTLS record.
|
|
* origBuf is the decrypted ssl record content and is expected to contain
|
|
* complete handshake records
|
|
* Caller must hold the handshake and RecvBuf locks.
|
|
*
|
|
* Note that this code uses msg_len for two purposes:
|
|
*
|
|
* (1) To pass the length to ssl3_HandleHandshakeMessage()
|
|
* (2) To carry the length of a message currently being reassembled
|
|
*
|
|
* However, unlike ssl3_HandleHandshake(), it is not used to carry
|
|
* the state of reassembly (i.e., whether one is in progress). That
|
|
* is carried in recvdHighWater and recvdFragments.
|
|
*/
|
|
#define OFFSET_BYTE(o) (o / 8)
|
|
#define OFFSET_MASK(o) (1 << (o % 8))
|
|
|
|
SECStatus
|
|
dtls_HandleHandshake(sslSocket *ss, DTLSEpoch epoch, sslSequenceNumber seqNum,
|
|
sslBuffer *origBuf)
|
|
{
|
|
/* 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;
|
|
PRBool discarded = PR_FALSE;
|
|
|
|
ss->ssl3.hs.endOfFlight = PR_FALSE;
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
|
|
while (buf.len > 0) {
|
|
PRUint8 type;
|
|
PRUint32 message_length;
|
|
PRUint16 message_seq;
|
|
PRUint32 fragment_offset;
|
|
PRUint32 fragment_length;
|
|
PRUint32 offset;
|
|
|
|
if (buf.len < 12) {
|
|
PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
|
|
rv = SECFailure;
|
|
goto loser;
|
|
}
|
|
|
|
/* Parse the header */
|
|
type = buf.buf[0];
|
|
message_length = (buf.buf[1] << 16) | (buf.buf[2] << 8) | buf.buf[3];
|
|
message_seq = (buf.buf[4] << 8) | buf.buf[5];
|
|
fragment_offset = (buf.buf[6] << 16) | (buf.buf[7] << 8) | buf.buf[8];
|
|
fragment_length = (buf.buf[9] << 16) | (buf.buf[10] << 8) | buf.buf[11];
|
|
|
|
#define MAX_HANDSHAKE_MSG_LEN 0x1ffff /* 128k - 1 */
|
|
if (message_length > MAX_HANDSHAKE_MSG_LEN) {
|
|
(void)ssl3_DecodeError(ss);
|
|
PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
|
|
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;
|
|
goto loser;
|
|
}
|
|
|
|
/* Sanity check the packet contents */
|
|
if ((fragment_length + fragment_offset) > message_length) {
|
|
PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
|
|
rv = SECFailure;
|
|
goto loser;
|
|
}
|
|
|
|
/* If we're a server and we receive what appears to be a retried
|
|
* ClientHello, and we are expecting a ClientHello, move the receive
|
|
* sequence number forward. This allows for a retried ClientHello if we
|
|
* send a stateless HelloRetryRequest. */
|
|
if (message_seq > ss->ssl3.hs.recvMessageSeq &&
|
|
message_seq == 1 &&
|
|
fragment_offset == 0 &&
|
|
ss->ssl3.hs.ws == wait_client_hello &&
|
|
(SSLHandshakeType)type == ssl_hs_client_hello) {
|
|
SSL_TRC(5, ("%d: DTLS[%d]: Received apparent 2nd ClientHello",
|
|
SSL_GETPID(), ss->fd));
|
|
ss->ssl3.hs.recvMessageSeq = 1;
|
|
ss->ssl3.hs.helloRetry = PR_TRUE;
|
|
}
|
|
|
|
/* There are three ways we could not be ready for this packet.
|
|
*
|
|
* 1. It's a partial next message.
|
|
* 2. It's a partial or complete message beyond the next
|
|
* 3. It's a message we've already seen
|
|
*
|
|
* If it's the complete next message we accept it right away.
|
|
* This is the common case for short messages
|
|
*/
|
|
if ((message_seq == ss->ssl3.hs.recvMessageSeq) &&
|
|
(fragment_offset == 0) &&
|
|
(fragment_length == message_length)) {
|
|
/* Complete next message. Process immediately */
|
|
ss->ssl3.hs.msg_type = (SSLHandshakeType)type;
|
|
ss->ssl3.hs.msg_len = message_length;
|
|
|
|
rv = dtls_HandleHandshakeMessage(ss, buf.buf,
|
|
buf.len == fragment_length);
|
|
if (rv == SECFailure) {
|
|
goto loser;
|
|
}
|
|
} else {
|
|
if (message_seq < ss->ssl3.hs.recvMessageSeq) {
|
|
/* Case 3: we do an immediate retransmit if we're
|
|
* in a waiting state. */
|
|
rv = dtls_RetransmitDetected(ss);
|
|
goto loser;
|
|
} else if (message_seq > ss->ssl3.hs.recvMessageSeq) {
|
|
/* Case 2
|
|
*
|
|
* Ignore this message. This means we don't handle out of
|
|
* order complete messages that well, but we're still
|
|
* compliant and this probably does not happen often
|
|
*
|
|
* XXX OK for now. Maybe do something smarter at some point?
|
|
*/
|
|
SSL_TRC(10, ("%d: SSL3[%d]: dtls_HandleHandshake, discarding handshake message",
|
|
SSL_GETPID(), ss->fd));
|
|
discarded = PR_TRUE;
|
|
} else {
|
|
PRInt32 end = fragment_offset + fragment_length;
|
|
|
|
/* Case 1
|
|
*
|
|
* Buffer the fragment for reassembly
|
|
*/
|
|
/* Make room for the message */
|
|
if (ss->ssl3.hs.recvdHighWater == -1) {
|
|
PRUint32 map_length = OFFSET_BYTE(message_length) + 1;
|
|
|
|
rv = sslBuffer_Grow(&ss->ssl3.hs.msg_body, message_length);
|
|
if (rv != SECSuccess)
|
|
goto loser;
|
|
/* Make room for the fragment map */
|
|
rv = sslBuffer_Grow(&ss->ssl3.hs.recvdFragments,
|
|
map_length);
|
|
if (rv != SECSuccess)
|
|
goto loser;
|
|
|
|
/* Reset the reassembly map */
|
|
ss->ssl3.hs.recvdHighWater = 0;
|
|
PORT_Memset(ss->ssl3.hs.recvdFragments.buf, 0,
|
|
ss->ssl3.hs.recvdFragments.space);
|
|
ss->ssl3.hs.msg_type = (SSLHandshakeType)type;
|
|
ss->ssl3.hs.msg_len = message_length;
|
|
}
|
|
|
|
/* If we have a message length mismatch, abandon the reassembly
|
|
* in progress and hope that the next retransmit will give us
|
|
* something sane
|
|
*/
|
|
if (message_length != ss->ssl3.hs.msg_len) {
|
|
ss->ssl3.hs.recvdHighWater = -1;
|
|
PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
|
|
rv = SECFailure;
|
|
goto loser;
|
|
}
|
|
|
|
/* Now copy this fragment into the buffer. */
|
|
if (end > ss->ssl3.hs.recvdHighWater) {
|
|
PORT_Memcpy(ss->ssl3.hs.msg_body.buf + fragment_offset,
|
|
buf.buf, fragment_length);
|
|
}
|
|
|
|
/* This logic is a bit tricky. We have two values for
|
|
* reassembly state:
|
|
*
|
|
* - recvdHighWater contains the highest contiguous number of
|
|
* bytes received
|
|
* - recvdFragments contains a bitmask of packets received
|
|
* above recvdHighWater
|
|
*
|
|
* This avoids having to fill in the bitmask in the common
|
|
* case of adjacent fragments received in sequence
|
|
*/
|
|
if (fragment_offset <= (unsigned int)ss->ssl3.hs.recvdHighWater) {
|
|
/* Either this is the adjacent fragment or an overlapping
|
|
* fragment */
|
|
if (end > ss->ssl3.hs.recvdHighWater) {
|
|
ss->ssl3.hs.recvdHighWater = end;
|
|
}
|
|
} else {
|
|
for (offset = fragment_offset; offset < end; offset++) {
|
|
ss->ssl3.hs.recvdFragments.buf[OFFSET_BYTE(offset)] |=
|
|
OFFSET_MASK(offset);
|
|
}
|
|
}
|
|
|
|
/* Now figure out the new high water mark if appropriate */
|
|
for (offset = ss->ssl3.hs.recvdHighWater;
|
|
offset < ss->ssl3.hs.msg_len; offset++) {
|
|
/* Note that this loop is not efficient, since it counts
|
|
* bit by bit. If we have a lot of out-of-order packets,
|
|
* we should optimize this */
|
|
if (ss->ssl3.hs.recvdFragments.buf[OFFSET_BYTE(offset)] &
|
|
OFFSET_MASK(offset)) {
|
|
ss->ssl3.hs.recvdHighWater++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If we have all the bytes, then we are good to go */
|
|
if (ss->ssl3.hs.recvdHighWater == ss->ssl3.hs.msg_len) {
|
|
rv = dtls_HandleHandshakeMessage(ss, ss->ssl3.hs.msg_body.buf,
|
|
buf.len == fragment_length);
|
|
|
|
if (rv == SECFailure) {
|
|
goto loser;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
buf.buf += fragment_length;
|
|
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
|
|
* 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, SSLContentType ct,
|
|
const PRUint8 *pIn, PRInt32 nIn)
|
|
{
|
|
SECStatus rv = SECSuccess;
|
|
DTLSQueuedMessage *msg = NULL;
|
|
ssl3CipherSpec *spec;
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
|
|
|
|
spec = ss->ssl3.cwSpec;
|
|
msg = dtls_AllocQueuedMessage(spec, ct, pIn, nIn);
|
|
|
|
if (!msg) {
|
|
PORT_SetError(SEC_ERROR_NO_MEMORY);
|
|
rv = SECFailure;
|
|
} else {
|
|
PR_APPEND_LINK(&msg->link, &ss->ssl3.hs.lastMessageFlight);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Add DTLS handshake message to the pending queue
|
|
* Empty the sendBuf buffer.
|
|
* Always set sendBuf.len to 0, even when returning SECFailure.
|
|
*
|
|
* Called from:
|
|
* ssl3_AppendHandshakeHeader()
|
|
* dtls_FlushHandshake()
|
|
*/
|
|
SECStatus
|
|
dtls_StageHandshakeMessage(sslSocket *ss)
|
|
{
|
|
SECStatus rv = SECSuccess;
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
|
|
|
|
/* This function is sometimes called when no data is actually to
|
|
* be staged, so just return SECSuccess. */
|
|
if (!ss->sec.ci.sendBuf.buf || !ss->sec.ci.sendBuf.len)
|
|
return rv;
|
|
|
|
rv = dtls_QueueMessage(ss, ssl_ct_handshake,
|
|
ss->sec.ci.sendBuf.buf, ss->sec.ci.sendBuf.len);
|
|
|
|
/* Whether we succeeded or failed, toss the old handshake data. */
|
|
ss->sec.ci.sendBuf.len = 0;
|
|
return rv;
|
|
}
|
|
|
|
/* Enqueue the handshake message in sendBuf (if any) and then
|
|
* transmit the resulting flight of handshake messages.
|
|
*
|
|
* Called from:
|
|
* ssl3_FlushHandshake()
|
|
*/
|
|
SECStatus
|
|
dtls_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags)
|
|
{
|
|
SECStatus rv = SECSuccess;
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
|
|
|
|
rv = dtls_StageHandshakeMessage(ss);
|
|
if (rv != SECSuccess)
|
|
return rv;
|
|
|
|
if (!(flags & ssl_SEND_FLAG_FORCE_INTO_BUFFER)) {
|
|
rv = dtls_TransmitMessageFlight(ss);
|
|
if (rv != SECSuccess) {
|
|
return rv;
|
|
}
|
|
|
|
if (!(flags & ssl_SEND_FLAG_NO_RETRANSMIT)) {
|
|
rv = dtls_StartRetransmitTimer(ss);
|
|
} else {
|
|
PORT_Assert(ss->version < SSL_LIBRARY_VERSION_TLS_1_3);
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* The callback for when the retransmit timer expires
|
|
*
|
|
* Called from:
|
|
* dtls_CheckTimer()
|
|
* dtls_HandleHandshake()
|
|
*/
|
|
static void
|
|
dtls_RetransmitTimerExpiredCb(sslSocket *ss)
|
|
{
|
|
SECStatus rv;
|
|
dtlsTimer *timer = ss->ssl3.hs.rtTimer;
|
|
ss->ssl3.hs.rtRetries++;
|
|
|
|
if (!(ss->ssl3.hs.rtRetries % 3)) {
|
|
/* If one of the messages was potentially greater than > MTU,
|
|
* then downgrade. Do this every time we have retransmitted a
|
|
* message twice, per RFC 6347 Sec. 4.1.1 */
|
|
dtls_SetMTU(ss, ss->ssl3.hs.maxMessageSent - 1);
|
|
}
|
|
|
|
rv = dtls_TransmitMessageFlight(ss);
|
|
if (rv == SECSuccess) {
|
|
/* Re-arm the timer */
|
|
timer->timeout *= 2;
|
|
if (timer->timeout > DTLS_RETRANSMIT_MAX_MS) {
|
|
timer->timeout = DTLS_RETRANSMIT_MAX_MS;
|
|
}
|
|
|
|
timer->started = PR_IntervalNow();
|
|
timer->cb = dtls_RetransmitTimerExpiredCb;
|
|
|
|
SSL_TRC(30,
|
|
("%d: SSL3[%d]: Retransmit #%d, next in %d",
|
|
SSL_GETPID(), ss->fd,
|
|
ss->ssl3.hs.rtRetries, timer->timeout));
|
|
}
|
|
/* else: OK for now. In future maybe signal the stack that we couldn't
|
|
* transmit. For now, let the read handle any real network errors */
|
|
}
|
|
|
|
#define DTLS_HS_HDR_LEN 12
|
|
#define DTLS_MIN_FRAGMENT (DTLS_HS_HDR_LEN + 1 + DTLS_MAX_EXPANSION)
|
|
|
|
/* Encrypt and encode a handshake message fragment. Flush the data out to the
|
|
* network if there is insufficient space for any fragment. */
|
|
static SECStatus
|
|
dtls_SendFragment(sslSocket *ss, DTLSQueuedMessage *msg, PRUint8 *data,
|
|
unsigned int len)
|
|
{
|
|
PRInt32 sent;
|
|
SECStatus rv;
|
|
|
|
PRINT_BUF(40, (ss, "dtls_SendFragment", data, len));
|
|
sent = ssl3_SendRecord(ss, msg->cwSpec, msg->type, data, len,
|
|
ssl_SEND_FLAG_FORCE_INTO_BUFFER);
|
|
if (sent != len) {
|
|
if (sent != -1) {
|
|
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
|
|
}
|
|
return SECFailure;
|
|
}
|
|
|
|
/* If another fragment won't fit, flush. */
|
|
if (ss->ssl3.mtu < ss->pendingBuf.len + DTLS_MIN_FRAGMENT) {
|
|
SSL_TRC(20, ("%d: DTLS[%d]: dtls_SendFragment: flush",
|
|
SSL_GETPID(), ss->fd));
|
|
rv = dtls_SendSavedWriteData(ss);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
}
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* Fragment a handshake message into multiple records and send them. */
|
|
static SECStatus
|
|
dtls_FragmentHandshake(sslSocket *ss, DTLSQueuedMessage *msg)
|
|
{
|
|
PRBool fragmentWritten = PR_FALSE;
|
|
PRUint16 msgSeq;
|
|
PRUint8 *fragment;
|
|
PRUint32 fragmentOffset = 0;
|
|
PRUint32 fragmentLen;
|
|
const PRUint8 *content = msg->data + DTLS_HS_HDR_LEN;
|
|
PRUint32 contentLen = msg->len - DTLS_HS_HDR_LEN;
|
|
SECStatus rv;
|
|
|
|
/* The headers consume 12 bytes so the smallest possible message (i.e., an
|
|
* empty one) is 12 bytes. */
|
|
PORT_Assert(msg->len >= DTLS_HS_HDR_LEN);
|
|
|
|
/* DTLS only supports fragmenting handshaking messages. */
|
|
PORT_Assert(msg->type == ssl_ct_handshake);
|
|
|
|
msgSeq = (msg->data[4] << 8) | msg->data[5];
|
|
|
|
/* do {} while() so that empty messages are sent at least once. */
|
|
do {
|
|
PRUint8 buf[DTLS_MAX_MTU]; /* >= than largest plausible MTU */
|
|
PRBool hasUnackedRange;
|
|
PRUint32 end;
|
|
|
|
hasUnackedRange = dtls_NextUnackedRange(ss, msgSeq,
|
|
fragmentOffset, contentLen,
|
|
&fragmentOffset, &end);
|
|
if (!hasUnackedRange) {
|
|
SSL_TRC(20, ("%d: SSL3[%d]: FragmentHandshake %d: all acknowledged",
|
|
SSL_GETPID(), ss->fd, msgSeq));
|
|
break;
|
|
}
|
|
|
|
SSL_TRC(20, ("%d: SSL3[%d]: FragmentHandshake %d: unacked=%u-%u",
|
|
SSL_GETPID(), ss->fd, msgSeq, fragmentOffset, end));
|
|
|
|
/* Cut down to the data we have available. */
|
|
PORT_Assert(fragmentOffset <= contentLen);
|
|
PORT_Assert(fragmentOffset <= end);
|
|
PORT_Assert(end <= contentLen);
|
|
fragmentLen = PR_MIN(end, contentLen) - fragmentOffset;
|
|
|
|
/* Limit further by the record size limit. Account for the header. */
|
|
fragmentLen = PR_MIN(fragmentLen,
|
|
msg->cwSpec->recordSizeLimit - DTLS_HS_HDR_LEN);
|
|
|
|
/* Reduce to the space remaining in the MTU. */
|
|
fragmentLen = PR_MIN(fragmentLen,
|
|
ss->ssl3.mtu - /* MTU estimate. */
|
|
ss->pendingBuf.len - /* Less any unsent records. */
|
|
DTLS_MAX_EXPANSION - /* Allow for expansion. */
|
|
DTLS_HS_HDR_LEN); /* And the handshake header. */
|
|
PORT_Assert(fragmentLen > 0 || fragmentOffset == 0);
|
|
|
|
/* Make totally sure that we will fit in the buffer. This should be
|
|
* impossible; DTLS_MAX_MTU should always be more than ss->ssl3.mtu. */
|
|
if (fragmentLen >= (DTLS_MAX_MTU - DTLS_HS_HDR_LEN)) {
|
|
PORT_Assert(0);
|
|
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
|
|
return SECFailure;
|
|
}
|
|
|
|
if (fragmentLen == contentLen) {
|
|
fragment = msg->data;
|
|
} else {
|
|
sslBuffer tmp = SSL_BUFFER_FIXED(buf, sizeof(buf));
|
|
|
|
/* Construct an appropriate-sized fragment */
|
|
/* Type, length, sequence */
|
|
rv = sslBuffer_Append(&tmp, msg->data, 6);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
/* Offset. */
|
|
rv = sslBuffer_AppendNumber(&tmp, fragmentOffset, 3);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
/* Length. */
|
|
rv = sslBuffer_AppendNumber(&tmp, fragmentLen, 3);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
/* Data. */
|
|
rv = sslBuffer_Append(&tmp, content + fragmentOffset, fragmentLen);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
fragment = SSL_BUFFER_BASE(&tmp);
|
|
}
|
|
|
|
/* Record that we are sending first, because encrypting
|
|
* increments the sequence number. */
|
|
rv = dtls13_RememberFragment(ss, &ss->ssl3.hs.dtlsSentHandshake,
|
|
msgSeq, fragmentOffset, fragmentLen,
|
|
msg->cwSpec->epoch,
|
|
msg->cwSpec->nextSeqNum);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
rv = dtls_SendFragment(ss, msg, fragment,
|
|
fragmentLen + DTLS_HS_HDR_LEN);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
fragmentWritten = PR_TRUE;
|
|
fragmentOffset += fragmentLen;
|
|
} while (fragmentOffset < contentLen);
|
|
|
|
if (!fragmentWritten) {
|
|
/* Nothing was written if we got here, so the whole message must have
|
|
* been acknowledged. Discard it. */
|
|
SSL_TRC(10, ("%d: SSL3[%d]: FragmentHandshake %d: removed",
|
|
SSL_GETPID(), ss->fd, msgSeq));
|
|
PR_REMOVE_LINK(&msg->link);
|
|
dtls_FreeHandshakeMessage(msg);
|
|
}
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* Transmit a flight of handshake messages, stuffing them
|
|
* into as few records as seems reasonable.
|
|
*
|
|
* TODO: Space separate UDP packets out a little.
|
|
*
|
|
* Called from:
|
|
* dtls_FlushHandshake()
|
|
* dtls_RetransmitTimerExpiredCb()
|
|
*/
|
|
SECStatus
|
|
dtls_TransmitMessageFlight(sslSocket *ss)
|
|
{
|
|
SECStatus rv = SECSuccess;
|
|
PRCList *msg_p;
|
|
|
|
SSL_TRC(10, ("%d: SSL3[%d]: dtls_TransmitMessageFlight",
|
|
SSL_GETPID(), ss->fd));
|
|
|
|
ssl_GetXmitBufLock(ss);
|
|
ssl_GetSpecReadLock(ss);
|
|
|
|
/* DTLS does not buffer its handshake messages in ss->pendingBuf, but rather
|
|
* in the lastMessageFlight structure. This is just a sanity check that some
|
|
* programming error hasn't inadvertantly stuffed something in
|
|
* ss->pendingBuf. This function uses ss->pendingBuf temporarily and it
|
|
* needs to be empty to start.
|
|
*/
|
|
PORT_Assert(!ss->pendingBuf.len);
|
|
|
|
for (msg_p = PR_LIST_HEAD(&ss->ssl3.hs.lastMessageFlight);
|
|
msg_p != &ss->ssl3.hs.lastMessageFlight;) {
|
|
DTLSQueuedMessage *msg = (DTLSQueuedMessage *)msg_p;
|
|
|
|
/* Move the pointer forward so that the functions below are free to
|
|
* remove messages from the list. */
|
|
msg_p = PR_NEXT_LINK(msg_p);
|
|
|
|
/* Note: This function fragments messages so that each record is close
|
|
* to full. This produces fewer records, but it means that messages can
|
|
* be quite fragmented. Adding an extra flush here would push new
|
|
* messages into new records and reduce fragmentation. */
|
|
|
|
if (msg->type == ssl_ct_handshake) {
|
|
rv = dtls_FragmentHandshake(ss, msg);
|
|
} else {
|
|
PORT_Assert(!tls13_MaybeTls13(ss));
|
|
rv = dtls_SendFragment(ss, msg, msg->data, msg->len);
|
|
}
|
|
if (rv != SECSuccess) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Finally, flush any data that wasn't flushed already. */
|
|
if (rv == SECSuccess) {
|
|
rv = dtls_SendSavedWriteData(ss);
|
|
}
|
|
|
|
/* Give up the locks */
|
|
ssl_ReleaseSpecReadLock(ss);
|
|
ssl_ReleaseXmitBufLock(ss);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Flush the data in the pendingBuf and update the max message sent
|
|
* so we can adjust the MTU estimate if we need to.
|
|
* Wrapper for ssl_SendSavedWriteData.
|
|
*
|
|
* Called from dtls_TransmitMessageFlight()
|
|
*/
|
|
static SECStatus
|
|
dtls_SendSavedWriteData(sslSocket *ss)
|
|
{
|
|
PRInt32 sent;
|
|
|
|
sent = ssl_SendSavedWriteData(ss);
|
|
if (sent < 0)
|
|
return SECFailure;
|
|
|
|
/* We should always have complete writes b/c datagram sockets
|
|
* don't really block */
|
|
if (ss->pendingBuf.len > 0) {
|
|
ssl_MapLowLevelError(SSL_ERROR_SOCKET_WRITE_FAILURE);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* Update the largest message sent so we can adjust the MTU
|
|
* estimate if necessary */
|
|
if (sent > ss->ssl3.hs.maxMessageSent)
|
|
ss->ssl3.hs.maxMessageSent = sent;
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
void
|
|
dtls_InitTimers(sslSocket *ss)
|
|
{
|
|
unsigned int i;
|
|
dtlsTimer **timers[PR_ARRAY_SIZE(ss->ssl3.hs.timers)] = {
|
|
&ss->ssl3.hs.rtTimer,
|
|
&ss->ssl3.hs.ackTimer,
|
|
&ss->ssl3.hs.hdTimer
|
|
};
|
|
static const char *timerLabels[] = {
|
|
"retransmit", "ack", "holddown"
|
|
};
|
|
|
|
PORT_Assert(PR_ARRAY_SIZE(timers) == PR_ARRAY_SIZE(timerLabels));
|
|
for (i = 0; i < PR_ARRAY_SIZE(ss->ssl3.hs.timers); ++i) {
|
|
*timers[i] = &ss->ssl3.hs.timers[i];
|
|
ss->ssl3.hs.timers[i].label = timerLabels[i];
|
|
}
|
|
}
|
|
|
|
SECStatus
|
|
dtls_StartTimer(sslSocket *ss, dtlsTimer *timer, PRUint32 time, DTLSTimerCb cb)
|
|
{
|
|
PORT_Assert(timer->cb == NULL);
|
|
|
|
SSL_TRC(10, ("%d: SSL3[%d]: %s dtls_StartTimer %s timeout=%d",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss), timer->label, time));
|
|
|
|
timer->started = PR_IntervalNow();
|
|
timer->timeout = time;
|
|
timer->cb = cb;
|
|
return SECSuccess;
|
|
}
|
|
|
|
SECStatus
|
|
dtls_RestartTimer(sslSocket *ss, dtlsTimer *timer)
|
|
{
|
|
timer->started = PR_IntervalNow();
|
|
return SECSuccess;
|
|
}
|
|
|
|
PRBool
|
|
dtls_TimerActive(sslSocket *ss, dtlsTimer *timer)
|
|
{
|
|
return timer->cb != NULL;
|
|
}
|
|
/* Start a timer for retransmission. */
|
|
static SECStatus
|
|
dtls_StartRetransmitTimer(sslSocket *ss)
|
|
{
|
|
ss->ssl3.hs.rtRetries = 0;
|
|
return dtls_StartTimer(ss, ss->ssl3.hs.rtTimer,
|
|
DTLS_RETRANSMIT_INITIAL_MS,
|
|
dtls_RetransmitTimerExpiredCb);
|
|
}
|
|
|
|
/* Start a timer for holding an old cipher spec. */
|
|
SECStatus
|
|
dtls_StartHolddownTimer(sslSocket *ss)
|
|
{
|
|
ss->ssl3.hs.rtRetries = 0;
|
|
return dtls_StartTimer(ss, ss->ssl3.hs.rtTimer,
|
|
DTLS_RETRANSMIT_FINISHED_MS,
|
|
dtls_FinishedTimerCb);
|
|
}
|
|
|
|
/* Cancel a pending timer
|
|
*
|
|
* Called from:
|
|
* dtls_HandleHandshake()
|
|
* dtls_CheckTimer()
|
|
*/
|
|
void
|
|
dtls_CancelTimer(sslSocket *ss, dtlsTimer *timer)
|
|
{
|
|
SSL_TRC(30, ("%d: SSL3[%d]: %s dtls_CancelTimer %s",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss),
|
|
timer->label));
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
|
|
|
|
timer->cb = NULL;
|
|
}
|
|
|
|
static void
|
|
dtls_CancelAllTimers(sslSocket *ss)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < PR_ARRAY_SIZE(ss->ssl3.hs.timers); ++i) {
|
|
dtls_CancelTimer(ss, &ss->ssl3.hs.timers[i]);
|
|
}
|
|
}
|
|
|
|
/* Check the pending timer and fire the callback if it expired
|
|
*
|
|
* Called from ssl3_GatherCompleteHandshake()
|
|
*/
|
|
void
|
|
dtls_CheckTimer(sslSocket *ss)
|
|
{
|
|
unsigned int i;
|
|
SSL_TRC(30, ("%d: SSL3[%d]: dtls_CheckTimer (%s)",
|
|
SSL_GETPID(), ss->fd, ss->sec.isServer ? "server" : "client"));
|
|
|
|
ssl_GetSSL3HandshakeLock(ss);
|
|
|
|
for (i = 0; i < PR_ARRAY_SIZE(ss->ssl3.hs.timers); ++i) {
|
|
dtlsTimer *timer = &ss->ssl3.hs.timers[i];
|
|
if (!timer->cb) {
|
|
continue;
|
|
}
|
|
|
|
if ((PR_IntervalNow() - timer->started) >=
|
|
PR_MillisecondsToInterval(timer->timeout)) {
|
|
/* Timer has expired */
|
|
DTLSTimerCb cb = timer->cb;
|
|
|
|
SSL_TRC(10, ("%d: SSL3[%d]: %s firing timer %s",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss),
|
|
timer->label));
|
|
|
|
/* Cancel the timer so that we can call the CB safely */
|
|
dtls_CancelTimer(ss, timer);
|
|
|
|
/* Now call the CB */
|
|
cb(ss);
|
|
}
|
|
}
|
|
ssl_ReleaseSSL3HandshakeLock(ss);
|
|
}
|
|
|
|
/* The callback to fire when the holddown timer for the Finished
|
|
* message expires and we can delete it
|
|
*
|
|
* Called from dtls_CheckTimer()
|
|
*/
|
|
static void
|
|
dtls_FinishedTimerCb(sslSocket *ss)
|
|
{
|
|
dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
|
|
}
|
|
|
|
/* Cancel the Finished hold-down timer and destroy the
|
|
* pending cipher spec. Note that this means that
|
|
* successive rehandshakes will fail if the Finished is
|
|
* lost.
|
|
*
|
|
* XXX OK for now. Figure out how to handle the combination
|
|
* of Finished lost and rehandshake
|
|
*/
|
|
void
|
|
dtls_RehandshakeCleanup(sslSocket *ss)
|
|
{
|
|
/* Skip this if we are handling a second ClientHello. */
|
|
if (ss->ssl3.hs.helloRetry) {
|
|
return;
|
|
}
|
|
PORT_Assert((ss->version < SSL_LIBRARY_VERSION_TLS_1_3));
|
|
dtls_CancelAllTimers(ss);
|
|
dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
|
|
ss->ssl3.hs.sendMessageSeq = 0;
|
|
ss->ssl3.hs.recvMessageSeq = 0;
|
|
}
|
|
|
|
/* Set the MTU to the next step less than or equal to the
|
|
* advertised value. Also used to downgrade the MTU by
|
|
* doing dtls_SetMTU(ss, biggest packet set).
|
|
*
|
|
* Passing 0 means set this to the largest MTU known
|
|
* (effectively resetting the PMTU backoff value).
|
|
*
|
|
* Called by:
|
|
* ssl3_InitState()
|
|
* dtls_RetransmitTimerExpiredCb()
|
|
*/
|
|
void
|
|
dtls_SetMTU(sslSocket *ss, PRUint16 advertised)
|
|
{
|
|
int i;
|
|
|
|
if (advertised == 0) {
|
|
ss->ssl3.mtu = COMMON_MTU_VALUES[0];
|
|
SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < PR_ARRAY_SIZE(COMMON_MTU_VALUES); i++) {
|
|
if (COMMON_MTU_VALUES[i] <= advertised) {
|
|
ss->ssl3.mtu = COMMON_MTU_VALUES[i];
|
|
SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Fallback */
|
|
ss->ssl3.mtu = COMMON_MTU_VALUES[PR_ARRAY_SIZE(COMMON_MTU_VALUES) - 1];
|
|
SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
|
|
}
|
|
|
|
/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a
|
|
* DTLS hello_verify_request
|
|
* Caller must hold Handshake and RecvBuf locks.
|
|
*/
|
|
SECStatus
|
|
dtls_HandleHelloVerifyRequest(sslSocket *ss, PRUint8 *b, PRUint32 length)
|
|
{
|
|
int errCode = SSL_ERROR_RX_MALFORMED_HELLO_VERIFY_REQUEST;
|
|
SECStatus rv;
|
|
SSL3ProtocolVersion temp;
|
|
SSL3AlertDescription desc = illegal_parameter;
|
|
|
|
SSL_TRC(3, ("%d: SSL3[%d]: handle hello_verify_request handshake",
|
|
SSL_GETPID(), ss->fd));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
|
|
if (ss->ssl3.hs.ws != wait_server_hello) {
|
|
errCode = SSL_ERROR_RX_UNEXPECTED_HELLO_VERIFY_REQUEST;
|
|
desc = unexpected_message;
|
|
goto alert_loser;
|
|
}
|
|
|
|
dtls_ReceivedFirstMessageInFlight(ss);
|
|
|
|
/* The version.
|
|
*
|
|
* RFC 4347 required that you verify that the server versions
|
|
* match (Section 4.2.1) in the HelloVerifyRequest and the
|
|
* ServerHello.
|
|
*
|
|
* RFC 6347 suggests (SHOULD) that servers always use 1.0 in
|
|
* HelloVerifyRequest and allows the versions not to match,
|
|
* especially when 1.2 is being negotiated.
|
|
*
|
|
* Therefore we do not do anything to enforce a match, just
|
|
* read and check that this value is sane.
|
|
*/
|
|
rv = ssl_ClientReadVersion(ss, &b, &length, &temp);
|
|
if (rv != SECSuccess) {
|
|
goto loser; /* alert has been sent */
|
|
}
|
|
|
|
/* Read the cookie.
|
|
* IMPORTANT: The value of ss->ssl3.hs.cookie is only valid while the
|
|
* HelloVerifyRequest message remains valid. */
|
|
rv = ssl3_ConsumeHandshakeVariable(ss, &ss->ssl3.hs.cookie, 1, &b, &length);
|
|
if (rv != SECSuccess) {
|
|
goto loser; /* alert has been sent */
|
|
}
|
|
if (ss->ssl3.hs.cookie.len > DTLS_COOKIE_BYTES) {
|
|
desc = decode_error;
|
|
goto alert_loser; /* malformed. */
|
|
}
|
|
|
|
ssl_GetXmitBufLock(ss); /*******************************/
|
|
|
|
/* Now re-send the client hello */
|
|
rv = ssl3_SendClientHello(ss, client_hello_retransmit);
|
|
|
|
ssl_ReleaseXmitBufLock(ss); /*******************************/
|
|
|
|
if (rv == SECSuccess)
|
|
return rv;
|
|
|
|
alert_loser:
|
|
(void)SSL3_SendAlert(ss, alert_fatal, desc);
|
|
|
|
loser:
|
|
ssl_MapLowLevelError(errCode);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* Initialize the DTLS anti-replay window
|
|
*
|
|
* Called from:
|
|
* ssl3_SetupPendingCipherSpec()
|
|
* ssl3_InitCipherSpec()
|
|
*/
|
|
void
|
|
dtls_InitRecvdRecords(DTLSRecvdRecords *records)
|
|
{
|
|
PORT_Memset(records->data, 0, sizeof(records->data));
|
|
records->left = 0;
|
|
records->right = DTLS_RECVD_RECORDS_WINDOW - 1;
|
|
}
|
|
|
|
/*
|
|
* Has this DTLS record been received? Return values are:
|
|
* -1 -- out of range to the left
|
|
* 0 -- not received yet
|
|
* 1 -- replay
|
|
*
|
|
* Called from: ssl3_HandleRecord()
|
|
*/
|
|
int
|
|
dtls_RecordGetRecvd(const DTLSRecvdRecords *records, sslSequenceNumber seq)
|
|
{
|
|
PRUint64 offset;
|
|
|
|
/* Out of range to the left */
|
|
if (seq < records->left) {
|
|
return -1;
|
|
}
|
|
|
|
/* Out of range to the right; since we advance the window on
|
|
* receipt, that means that this packet has not been received
|
|
* yet */
|
|
if (seq > records->right)
|
|
return 0;
|
|
|
|
offset = seq % DTLS_RECVD_RECORDS_WINDOW;
|
|
|
|
return !!(records->data[offset / 8] & (1 << (offset % 8)));
|
|
}
|
|
|
|
/* Update the DTLS anti-replay window
|
|
*
|
|
* Called from ssl3_HandleRecord()
|
|
*/
|
|
void
|
|
dtls_RecordSetRecvd(DTLSRecvdRecords *records, sslSequenceNumber seq)
|
|
{
|
|
PRUint64 offset;
|
|
|
|
if (seq < records->left)
|
|
return;
|
|
|
|
if (seq > records->right) {
|
|
sslSequenceNumber new_left;
|
|
sslSequenceNumber new_right;
|
|
sslSequenceNumber right;
|
|
|
|
/* Slide to the right; this is the tricky part
|
|
*
|
|
* 1. new_top is set to have room for seq, on the
|
|
* next byte boundary by setting the right 8
|
|
* bits of seq
|
|
* 2. new_left is set to compensate.
|
|
* 3. Zero all bits between top and new_top. Since
|
|
* this is a ring, this zeroes everything as-yet
|
|
* unseen. Because we always operate on byte
|
|
* boundaries, we can zero one byte at a time
|
|
*/
|
|
new_right = seq | 0x07;
|
|
new_left = (new_right - DTLS_RECVD_RECORDS_WINDOW) + 1;
|
|
|
|
if (new_right > records->right + DTLS_RECVD_RECORDS_WINDOW) {
|
|
PORT_Memset(records->data, 0, sizeof(records->data));
|
|
} else {
|
|
for (right = records->right + 8; right <= new_right; right += 8) {
|
|
offset = right % DTLS_RECVD_RECORDS_WINDOW;
|
|
records->data[offset / 8] = 0;
|
|
}
|
|
}
|
|
|
|
records->right = new_right;
|
|
records->left = new_left;
|
|
}
|
|
|
|
offset = seq % DTLS_RECVD_RECORDS_WINDOW;
|
|
|
|
records->data[offset / 8] |= (1 << (offset % 8));
|
|
}
|
|
|
|
SECStatus
|
|
DTLS_GetHandshakeTimeout(PRFileDesc *socket, PRIntervalTime *timeout)
|
|
{
|
|
sslSocket *ss = NULL;
|
|
PRBool found = PR_FALSE;
|
|
PRIntervalTime now = PR_IntervalNow();
|
|
PRIntervalTime to;
|
|
unsigned int i;
|
|
|
|
*timeout = PR_INTERVAL_NO_TIMEOUT;
|
|
|
|
ss = ssl_FindSocket(socket);
|
|
|
|
if (!ss) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
if (!IS_DTLS(ss)) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
for (i = 0; i < PR_ARRAY_SIZE(ss->ssl3.hs.timers); ++i) {
|
|
PRIntervalTime elapsed;
|
|
PRIntervalTime desired;
|
|
dtlsTimer *timer = &ss->ssl3.hs.timers[i];
|
|
|
|
if (!timer->cb) {
|
|
continue;
|
|
}
|
|
found = PR_TRUE;
|
|
|
|
elapsed = now - timer->started;
|
|
desired = PR_MillisecondsToInterval(timer->timeout);
|
|
if (elapsed > desired) {
|
|
/* Timer expired */
|
|
*timeout = PR_INTERVAL_NO_WAIT;
|
|
return SECSuccess;
|
|
} else {
|
|
to = desired - elapsed;
|
|
}
|
|
|
|
if (*timeout > to) {
|
|
*timeout = to;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
PORT_SetError(SSL_ERROR_NO_TIMERS_FOUND);
|
|
return SECFailure;
|
|
}
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
PRBool
|
|
dtls_IsLongHeader(SSL3ProtocolVersion version, PRUint8 firstOctet)
|
|
{
|
|
#ifndef UNSAFE_FUZZER_MODE
|
|
return version < SSL_LIBRARY_VERSION_TLS_1_3 ||
|
|
firstOctet == ssl_ct_handshake ||
|
|
firstOctet == ssl_ct_ack ||
|
|
firstOctet == ssl_ct_alert;
|
|
#else
|
|
return PR_TRUE;
|
|
#endif
|
|
}
|
|
|
|
DTLSEpoch
|
|
dtls_ReadEpoch(const ssl3CipherSpec *crSpec, const PRUint8 *hdr)
|
|
{
|
|
DTLSEpoch epoch;
|
|
DTLSEpoch maxEpoch;
|
|
DTLSEpoch partial;
|
|
|
|
if (dtls_IsLongHeader(crSpec->version, hdr[0])) {
|
|
return ((DTLSEpoch)hdr[3] << 8) | hdr[4];
|
|
}
|
|
|
|
/* A lot of how we recover the epoch here will depend on how we plan to
|
|
* manage KeyUpdate. In the case that we decide to install a new read spec
|
|
* as a KeyUpdate is handled, crSpec will always be the highest epoch we can
|
|
* possibly receive. That makes this easier to manage. */
|
|
if ((hdr[0] & 0xe0) == 0x20) {
|
|
/* Use crSpec->epoch, or crSpec->epoch - 1 if the last bit differs. */
|
|
if (((hdr[0] >> 4) & 1) == (crSpec->epoch & 1)) {
|
|
return crSpec->epoch;
|
|
}
|
|
return crSpec->epoch - 1;
|
|
}
|
|
|
|
/* dtls_GatherData should ensure that this works. */
|
|
PORT_Assert(hdr[0] == ssl_ct_application_data);
|
|
|
|
/* This uses the same method as is used to recover the sequence number in
|
|
* dtls_ReadSequenceNumber, except that the maximum value is set to the
|
|
* current epoch. */
|
|
partial = hdr[1] >> 6;
|
|
maxEpoch = PR_MAX(crSpec->epoch, 3);
|
|
epoch = (maxEpoch & 0xfffc) | partial;
|
|
if (partial > (maxEpoch & 0x03)) {
|
|
epoch -= 4;
|
|
}
|
|
return epoch;
|
|
}
|
|
|
|
static sslSequenceNumber
|
|
dtls_ReadSequenceNumber(const ssl3CipherSpec *spec, const PRUint8 *hdr)
|
|
{
|
|
sslSequenceNumber cap;
|
|
sslSequenceNumber partial;
|
|
sslSequenceNumber seqNum;
|
|
sslSequenceNumber mask;
|
|
|
|
if (dtls_IsLongHeader(spec->version, hdr[0])) {
|
|
static const unsigned int seqNumOffset = 5; /* type, version, epoch */
|
|
static const unsigned int seqNumLength = 6;
|
|
sslReader r = SSL_READER(hdr + seqNumOffset, seqNumLength);
|
|
(void)sslRead_ReadNumber(&r, seqNumLength, &seqNum);
|
|
return seqNum;
|
|
}
|
|
|
|
/* Only the least significant bits of the sequence number is available here.
|
|
* This recovers the value based on the next expected sequence number.
|
|
*
|
|
* This works by determining the maximum possible sequence number, which is
|
|
* half the range of possible values above the expected next value (the
|
|
* expected next value is in |spec->seqNum|). Then, the last part of the
|
|
* sequence number is replaced. If that causes the value to exceed the
|
|
* maximum, subtract an entire range.
|
|
*/
|
|
if ((hdr[0] & 0xe0) == 0x20) {
|
|
/* A 12-bit sequence number. */
|
|
cap = spec->nextSeqNum + (1ULL << 11);
|
|
partial = (((sslSequenceNumber)hdr[0] & 0xf) << 8) |
|
|
(sslSequenceNumber)hdr[1];
|
|
mask = (1ULL << 12) - 1;
|
|
} else {
|
|
/* A 30-bit sequence number. */
|
|
cap = spec->nextSeqNum + (1ULL << 29);
|
|
partial = (((sslSequenceNumber)hdr[1] & 0x3f) << 24) |
|
|
((sslSequenceNumber)hdr[2] << 16) |
|
|
((sslSequenceNumber)hdr[3] << 8) |
|
|
(sslSequenceNumber)hdr[4];
|
|
mask = (1ULL << 30) - 1;
|
|
}
|
|
seqNum = (cap & ~mask) | partial;
|
|
/* The second check prevents the value from underflowing if we get a large
|
|
* gap at the start of a connection, where this subtraction would cause the
|
|
* sequence number to wrap to near UINT64_MAX. */
|
|
if ((partial > (cap & mask)) && (seqNum > mask)) {
|
|
seqNum -= mask + 1;
|
|
}
|
|
return seqNum;
|
|
}
|
|
|
|
/*
|
|
* DTLS relevance checks:
|
|
* Note that this code currently ignores all out-of-epoch packets,
|
|
* which means we lose some in the case of rehandshake +
|
|
* loss/reordering. Since DTLS is explicitly unreliable, this
|
|
* seems like a good tradeoff for implementation effort and is
|
|
* consistent with the guidance of RFC 6347 Sections 4.1 and 4.2.4.1.
|
|
*
|
|
* If the packet is not relevant, this function returns PR_FALSE. If the packet
|
|
* is relevant, this function returns PR_TRUE and sets |*seqNumOut| to the
|
|
* packet sequence number (removing the epoch).
|
|
*/
|
|
PRBool
|
|
dtls_IsRelevant(sslSocket *ss, const ssl3CipherSpec *spec,
|
|
const SSL3Ciphertext *cText,
|
|
sslSequenceNumber *seqNumOut)
|
|
{
|
|
sslSequenceNumber seqNum = dtls_ReadSequenceNumber(spec, cText->hdr);
|
|
if (dtls_RecordGetRecvd(&spec->recvdRecords, seqNum) != 0) {
|
|
SSL_TRC(10, ("%d: SSL3[%d]: dtls_IsRelevant, rejecting "
|
|
"potentially replayed packet",
|
|
SSL_GETPID(), ss->fd));
|
|
return PR_FALSE;
|
|
}
|
|
|
|
*seqNumOut = seqNum;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
void
|
|
dtls_ReceivedFirstMessageInFlight(sslSocket *ss)
|
|
{
|
|
if (!IS_DTLS(ss))
|
|
return;
|
|
|
|
/* At this point we are advancing our state machine, so we can free our last
|
|
* flight of messages. */
|
|
if (ss->ssl3.hs.ws != idle_handshake ||
|
|
ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) {
|
|
/* We need to keep our last flight around in DTLS 1.2 and below,
|
|
* so we can retransmit it in response to other people's
|
|
* retransmits. */
|
|
dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
|
|
|
|
/* Reset the timer to the initial value if the retry counter
|
|
* is 0, per RFC 6347, Sec. 4.2.4.1 */
|
|
dtls_CancelTimer(ss, ss->ssl3.hs.rtTimer);
|
|
if (ss->ssl3.hs.rtRetries == 0) {
|
|
ss->ssl3.hs.rtTimer->timeout = DTLS_RETRANSMIT_INITIAL_MS;
|
|
}
|
|
}
|
|
|
|
/* Empty the ACK queue (TLS 1.3 only). */
|
|
ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL);
|
|
}
|