868 lines
No EOL
29 KiB
C
868 lines
No EOL
29 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 1.3 Protocol
|
|
*/
|
|
|
|
#include "ssl.h"
|
|
#include "sslimpl.h"
|
|
#include "sslproto.h"
|
|
#include "keyhi.h"
|
|
#include "pk11func.h"
|
|
|
|
/*Figure 3: DTLS 1.3 Unified Header */
|
|
|
|
/*
|
|
* 0 1 2 3 4 5 6 7
|
|
* +-+-+-+-+-+-+-+-+
|
|
* |0|0|1|C|S|L|E E|
|
|
* +-+-+-+-+-+-+-+-+
|
|
* | Connection ID | Legend:
|
|
* | (if any, |
|
|
* / length as / C - CID present
|
|
* | negotiated) | S - Sequence number length
|
|
* +-+-+-+-+-+-+-+-+ L - Length present
|
|
* | 8 or 16 bit | E - Epoch
|
|
* |Sequence Number|
|
|
* +-+-+-+-+-+-+-+-+
|
|
* | 16 bit Length |
|
|
* | (if present) |
|
|
* +-+-+-+-+-+-+-+-+
|
|
*/
|
|
|
|
// E: The two low bits (0x03) include the low order two bits of the epoch.
|
|
#define MASK_TWO_LOW_BITS 0x3
|
|
// Fixed Bits: The three high bits of the first byte of the unified header are set to 001.
|
|
// The C bit is set if the Connection ID is present.
|
|
// The S bit (0x08) indicates the size of the sequence number, here 1 stands for 16 bits
|
|
// The L bit (0x04) is set if the length is present.
|
|
// The EE bits - mask of the epoch
|
|
|
|
// 0x2c = 0b001-0-1-1-00
|
|
// 001-C-S-L-EE
|
|
#define UNIFIED_HEADER_LONG 0x2c
|
|
// 0x20 = 0b001-0-0-1-00
|
|
// 001-C-S-L-EE
|
|
// The difference between the long and short header is in the S bit (1 for long, 0 for short).
|
|
// The S bit (0x08) indicates the size of the sequence number, here 0 stands for 8 bits
|
|
#define UNIFIED_HEADER_SHORT 0x20
|
|
|
|
// The masks to get the 8 (MASK_SEQUENCE_NUMBER_SHORT) or 16 bits (MASK_SEQUENCE_NUMBER_LONG) of the record sequence number.
|
|
#define MASK_SEQUENCE_NUMBER_SHORT 0xff
|
|
#define MASK_SEQUENCE_NUMBER_LONG 0xffff
|
|
|
|
/*The DTLS Record Layer - Figure 3 and further*/
|
|
SECStatus
|
|
dtls13_InsertCipherTextHeader(const sslSocket *ss, const ssl3CipherSpec *cwSpec,
|
|
sslBuffer *wrBuf, PRBool *needsLength)
|
|
{
|
|
/* Avoid using short records for the handshake. We pack multiple records
|
|
* into the one datagram for the handshake. */
|
|
|
|
/* The short header here means that the S bit is set to 0 (8-bit sequence number) */
|
|
if (ss->opt.enableDtlsShortHeader &&
|
|
cwSpec->epoch > TrafficKeyHandshake) {
|
|
*needsLength = PR_FALSE;
|
|
/* The short header is comprised of two octets in the form
|
|
* 0b001000eessssssss where 'e' is the low two bits of the
|
|
* epoch and 's' is the low 8 bits of the sequence number. */
|
|
PRUint8 ct = UNIFIED_HEADER_SHORT | ((uint64_t)cwSpec->epoch & MASK_TWO_LOW_BITS);
|
|
if (sslBuffer_AppendNumber(wrBuf, ct, sizeof(ct)) != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
PRUint8 seq = cwSpec->nextSeqNum & MASK_SEQUENCE_NUMBER_SHORT;
|
|
return sslBuffer_AppendNumber(wrBuf, seq, sizeof(seq));
|
|
}
|
|
|
|
PRUint8 ct = UNIFIED_HEADER_LONG | ((PRUint8)cwSpec->epoch & MASK_TWO_LOW_BITS);
|
|
if (sslBuffer_AppendNumber(wrBuf, ct, sizeof(ct)) != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
PRUint16 seq = cwSpec->nextSeqNum & MASK_SEQUENCE_NUMBER_LONG;
|
|
if (sslBuffer_AppendNumber(wrBuf, seq, sizeof(seq)) != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
*needsLength = PR_TRUE;
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* DTLS 1.3 Record map for ACK processing.
|
|
* This represents a single fragment, so a record which includes
|
|
* multiple fragments will have one entry for each fragment on the
|
|
* sender. We use the same structure on the receiver for convenience
|
|
* but the only value we actually use is |record|.
|
|
*/
|
|
typedef struct DTLSHandshakeRecordEntryStr {
|
|
PRCList link;
|
|
PRUint16 messageSeq; /* The handshake message sequence number. */
|
|
PRUint32 offset; /* The offset into the handshake message. */
|
|
PRUint32 length; /* The length of the fragment. */
|
|
/* DTLS adds an epoch and sequence number to the TLS record header. */
|
|
sslSequenceNumber record; /* The record (includes epoch). */
|
|
PRBool acked; /* Has this packet been acked. */
|
|
} DTLSHandshakeRecordEntry;
|
|
|
|
/*The sequence number is set to be the low order 48
|
|
bits of the 64 bit sequence number.*/
|
|
#define LENGTH_SEQ_NUMBER 48
|
|
|
|
/* Combine the epoch and sequence number into a single value. */
|
|
static inline sslSequenceNumber
|
|
dtls_CombineSequenceNumber(DTLSEpoch epoch, sslSequenceNumber seqNum)
|
|
{
|
|
PORT_Assert(seqNum <= RECORD_SEQ_MAX);
|
|
return ((sslSequenceNumber)epoch << LENGTH_SEQ_NUMBER) | seqNum;
|
|
}
|
|
|
|
SECStatus
|
|
dtls13_RememberFragment(sslSocket *ss,
|
|
PRCList *list,
|
|
PRUint32 sequence,
|
|
PRUint32 offset,
|
|
PRUint32 length,
|
|
DTLSEpoch epoch,
|
|
sslSequenceNumber record)
|
|
{
|
|
DTLSHandshakeRecordEntry *entry;
|
|
|
|
PORT_Assert(IS_DTLS(ss));
|
|
/* We should never send an empty fragment with offset > 0. */
|
|
PORT_Assert(length || !offset);
|
|
|
|
if (!tls13_MaybeTls13(ss)) {
|
|
return SECSuccess;
|
|
}
|
|
|
|
SSL_TRC(20, ("%d: SSL3[%d]: %s remembering %s record=%llx msg=%d offset=%d",
|
|
SSL_GETPID(), ss->fd,
|
|
SSL_ROLE(ss),
|
|
list == &ss->ssl3.hs.dtlsSentHandshake ? "sent" : "received",
|
|
dtls_CombineSequenceNumber(epoch, record), sequence, offset));
|
|
|
|
entry = PORT_ZAlloc(sizeof(DTLSHandshakeRecordEntry));
|
|
if (!entry) {
|
|
return SECFailure;
|
|
}
|
|
|
|
entry->messageSeq = sequence;
|
|
entry->offset = offset;
|
|
entry->length = length;
|
|
entry->record = dtls_CombineSequenceNumber(epoch, record);
|
|
entry->acked = PR_FALSE;
|
|
|
|
PR_APPEND_LINK(&entry->link, list);
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* RFC9147; section 7.1 */
|
|
SECStatus
|
|
dtls13_SendAck(sslSocket *ss)
|
|
{
|
|
sslBuffer buf = SSL_BUFFER_EMPTY;
|
|
SECStatus rv = SECSuccess;
|
|
PRCList *cursor;
|
|
PRInt32 sent;
|
|
unsigned int offset = 0;
|
|
|
|
SSL_TRC(10, ("%d: SSL3[%d]: Sending ACK",
|
|
SSL_GETPID(), ss->fd));
|
|
|
|
/* RecordNumber record_numbers<0..2^16-1>;
|
|
2 length bytes for the list of ACKs*/
|
|
PRUint32 sizeOfListACK = 2;
|
|
rv = sslBuffer_Skip(&buf, sizeOfListACK, &offset);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
for (cursor = PR_LIST_HEAD(&ss->ssl3.hs.dtlsRcvdHandshake);
|
|
cursor != &ss->ssl3.hs.dtlsRcvdHandshake;
|
|
cursor = PR_NEXT_LINK(cursor)) {
|
|
DTLSHandshakeRecordEntry *entry = (DTLSHandshakeRecordEntry *)cursor;
|
|
|
|
SSL_TRC(10, ("%d: SSL3[%d]: ACK for record=%llx",
|
|
SSL_GETPID(), ss->fd, entry->record));
|
|
|
|
/*See dtls_CombineSequenceNumber function */
|
|
PRUint64 epoch = entry->record >> 48;
|
|
PRUint64 seqNum = entry->record & 0xffffffffffff;
|
|
|
|
rv = sslBuffer_AppendNumber(&buf, epoch, 8);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = sslBuffer_AppendNumber(&buf, seqNum, 8);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
}
|
|
|
|
rv = sslBuffer_InsertLength(&buf, offset, sizeOfListACK);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
ssl_GetXmitBufLock(ss);
|
|
sent = ssl3_SendRecord(ss, NULL, ssl_ct_ack,
|
|
buf.buf, buf.len, 0);
|
|
ssl_ReleaseXmitBufLock(ss);
|
|
if (sent != buf.len) {
|
|
rv = SECFailure;
|
|
if (sent != -1) {
|
|
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
|
|
}
|
|
}
|
|
|
|
loser:
|
|
sslBuffer_Clear(&buf);
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
dtls13_SendAckCb(sslSocket *ss)
|
|
{
|
|
if (!IS_DTLS(ss)) {
|
|
return;
|
|
}
|
|
(void)dtls13_SendAck(ss);
|
|
}
|
|
|
|
/* Limits from RFC9147; section 4.5.3. */
|
|
PRBool
|
|
dtls13_AeadLimitReached(ssl3CipherSpec *spec)
|
|
{
|
|
if (spec->version >= SSL_LIBRARY_VERSION_TLS_1_3) {
|
|
switch (spec->cipherDef->calg) {
|
|
case ssl_calg_chacha20:
|
|
case ssl_calg_aes_gcm:
|
|
return spec->deprotectionFailures >= (1ULL << 36);
|
|
#ifdef UNSAFE_FUZZER_MODE
|
|
case ssl_calg_null:
|
|
return PR_FALSE;
|
|
#endif
|
|
default:
|
|
PORT_Assert(0);
|
|
break;
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
/* Zero length messages are very simple to check. */
|
|
static PRBool
|
|
dtls_IsEmptyMessageAcknowledged(sslSocket *ss, PRUint16 msgSeq, PRUint32 offset)
|
|
{
|
|
PRCList *cursor;
|
|
|
|
for (cursor = PR_LIST_HEAD(&ss->ssl3.hs.dtlsSentHandshake);
|
|
cursor != &ss->ssl3.hs.dtlsSentHandshake;
|
|
cursor = PR_NEXT_LINK(cursor)) {
|
|
DTLSHandshakeRecordEntry *entry = (DTLSHandshakeRecordEntry *)cursor;
|
|
if (!entry->acked || msgSeq != entry->messageSeq) {
|
|
continue;
|
|
}
|
|
/* Empty fragments are always offset 0. */
|
|
if (entry->length == 0) {
|
|
PORT_Assert(!entry->offset);
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
/* Take a range starting at |*start| and that start forwards based on the
|
|
* contents of the acknowedgement in |entry|. Only move if the acknowledged
|
|
* range overlaps |*start|. Return PR_TRUE if it moves. */
|
|
static PRBool
|
|
dtls_MoveUnackedStartForward(DTLSHandshakeRecordEntry *entry, PRUint32 *start)
|
|
{
|
|
/* This entry starts too late. */
|
|
if (*start < entry->offset) {
|
|
return PR_FALSE;
|
|
}
|
|
/* This entry ends too early. */
|
|
if (*start >= entry->offset + entry->length) {
|
|
return PR_FALSE;
|
|
}
|
|
*start = entry->offset + entry->length;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
/* Take a range ending at |*end| and move that end backwards based on the
|
|
* contents of the acknowedgement in |entry|. Only move if the acknowledged
|
|
* range overlaps |*end|. Return PR_TRUE if it moves. */
|
|
static PRBool
|
|
dtls_MoveUnackedEndBackward(DTLSHandshakeRecordEntry *entry, PRUint32 *end)
|
|
{
|
|
/* This entry ends too early. */
|
|
if (*end > entry->offset + entry->length) {
|
|
return PR_FALSE;
|
|
}
|
|
/* This entry starts too late. */
|
|
if (*end <= entry->offset) {
|
|
return PR_FALSE;
|
|
}
|
|
*end = entry->offset;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
/* Get the next contiguous range of unacknowledged bytes from the handshake
|
|
* message identified by |msgSeq|. The search starts at the offset in |offset|.
|
|
* |len| contains the full length of the message.
|
|
*
|
|
* Returns PR_TRUE if there is an unacknowledged range. In this case, values at
|
|
* |start| and |end| are modified to contain the range.
|
|
*
|
|
* Returns PR_FALSE if the message is entirely acknowledged from |offset|
|
|
* onwards.
|
|
*/
|
|
PRBool
|
|
dtls_NextUnackedRange(sslSocket *ss, PRUint16 msgSeq, PRUint32 offset,
|
|
PRUint32 len, PRUint32 *startOut, PRUint32 *endOut)
|
|
{
|
|
PRCList *cur_p;
|
|
PRBool done = PR_FALSE;
|
|
DTLSHandshakeRecordEntry *entry;
|
|
PRUint32 start;
|
|
PRUint32 end;
|
|
|
|
PORT_Assert(IS_DTLS(ss));
|
|
|
|
*startOut = offset;
|
|
*endOut = len;
|
|
if (!tls13_MaybeTls13(ss)) {
|
|
return PR_TRUE;
|
|
}
|
|
|
|
/* The message is empty. Use a simple search. */
|
|
if (!len) {
|
|
PORT_Assert(!offset);
|
|
return !dtls_IsEmptyMessageAcknowledged(ss, msgSeq, offset);
|
|
}
|
|
|
|
/* This iterates multiple times over the acknowledgments and only terminates
|
|
* when an entire iteration happens without start or end moving. If that
|
|
* happens without start and end crossing each other, then there is a range
|
|
* of unacknowledged data. If they meet, then the message is fully
|
|
* acknowledged. */
|
|
start = offset;
|
|
end = len;
|
|
while (!done) {
|
|
done = PR_TRUE;
|
|
for (cur_p = PR_LIST_HEAD(&ss->ssl3.hs.dtlsSentHandshake);
|
|
cur_p != &ss->ssl3.hs.dtlsSentHandshake;
|
|
cur_p = PR_NEXT_LINK(cur_p)) {
|
|
entry = (DTLSHandshakeRecordEntry *)cur_p;
|
|
if (!entry->acked || msgSeq != entry->messageSeq) {
|
|
continue;
|
|
}
|
|
|
|
if (dtls_MoveUnackedStartForward(entry, &start) ||
|
|
dtls_MoveUnackedEndBackward(entry, &end)) {
|
|
if (start >= end) {
|
|
/* The message is all acknowledged. */
|
|
return PR_FALSE;
|
|
}
|
|
/* Start over again and keep going until we don't move either
|
|
* start or end. */
|
|
done = PR_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
PORT_Assert(start < end);
|
|
|
|
*startOut = start;
|
|
*endOut = end;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
SECStatus
|
|
dtls13_SetupAcks(sslSocket *ss)
|
|
{
|
|
if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
|
|
return SECSuccess;
|
|
}
|
|
|
|
if (ss->ssl3.hs.endOfFlight) {
|
|
dtls_CancelTimer(ss, ss->ssl3.hs.ackTimer);
|
|
|
|
if (ss->ssl3.hs.ws == idle_handshake && ss->sec.isServer) {
|
|
SSL_TRC(10, ("%d: SSL3[%d]: dtls_HandleHandshake, sending ACK",
|
|
SSL_GETPID(), ss->fd));
|
|
return dtls13_SendAck(ss);
|
|
}
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* We need to send an ACK. */
|
|
if (!ss->ssl3.hs.ackTimer->cb) {
|
|
/* We're not armed, so arm. */
|
|
SSL_TRC(10, ("%d: SSL3[%d]: dtls_HandleHandshake, arming ack timer",
|
|
SSL_GETPID(), ss->fd));
|
|
return dtls_StartTimer(ss, ss->ssl3.hs.ackTimer,
|
|
DTLS_RETRANSMIT_INITIAL_MS / 4,
|
|
dtls13_SendAckCb);
|
|
}
|
|
/* The ack timer is already armed, so just return. */
|
|
return SECSuccess;
|
|
}
|
|
|
|
/*
|
|
* Special case processing for out-of-epoch records.
|
|
* This can only handle ACKs for now and everything else generates
|
|
* an error. In future, may also handle KeyUpdate.
|
|
*
|
|
* The error checking here is as follows:
|
|
*
|
|
* - If it's not encrypted, out of epoch stuff is just discarded.
|
|
* - If it's encrypted and the message is a piece of an application data, it's discarded.
|
|
* - Else out of epoch stuff causes an error.
|
|
*
|
|
*/
|
|
SECStatus
|
|
dtls13_HandleOutOfEpochRecord(sslSocket *ss, const ssl3CipherSpec *spec,
|
|
SSLContentType rType,
|
|
sslBuffer *databuf)
|
|
{
|
|
SECStatus rv;
|
|
sslBuffer buf = *databuf;
|
|
|
|
databuf->len = 0; /* Discard data whatever happens. */
|
|
PORT_Assert(IS_DTLS(ss));
|
|
PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3);
|
|
/* Can't happen, but double check. */
|
|
if (!IS_DTLS(ss) || (ss->version < SSL_LIBRARY_VERSION_TLS_1_3)) {
|
|
tls13_FatalError(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
|
|
return SECFailure;
|
|
}
|
|
SSL_TRC(30, ("%d: DTLS13[%d]: %s handles out of epoch record: type=%d", SSL_GETPID(),
|
|
ss->fd, SSL_ROLE(ss), rType));
|
|
|
|
if (rType == ssl_ct_ack) {
|
|
ssl_GetSSL3HandshakeLock(ss);
|
|
rv = dtls13_HandleAck(ss, &buf);
|
|
ssl_ReleaseSSL3HandshakeLock(ss);
|
|
PORT_Assert(databuf->len == 0);
|
|
return rv;
|
|
}
|
|
|
|
switch (spec->epoch) {
|
|
|
|
case TrafficKeyClearText:
|
|
/* Drop. */
|
|
return SECSuccess;
|
|
|
|
case TrafficKeyHandshake:
|
|
/* Drop out of order handshake messages, but if we are the
|
|
* server, we might have processed the client's Finished and
|
|
* moved on to application data keys, but the client has
|
|
* retransmitted Finished (e.g., because our ACK got lost.)
|
|
* We just retransmit the ACK to let the client complete. */
|
|
if (rType == ssl_ct_handshake) {
|
|
if ((ss->sec.isServer) &&
|
|
(ss->ssl3.hs.ws == idle_handshake)) {
|
|
PORT_Assert(dtls_TimerActive(ss, ss->ssl3.hs.hdTimer));
|
|
return dtls13_SendAck(ss);
|
|
}
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* This isn't a handshake record, so shouldn't be encrypted
|
|
* under the handshake key. */
|
|
break;
|
|
|
|
default:
|
|
if (rType == ssl_ct_application_data) {
|
|
return SECSuccess;
|
|
}
|
|
break;
|
|
}
|
|
|
|
SSL_TRC(10, ("%d: SSL3[%d]: unexpected out of epoch record type %d", SSL_GETPID(),
|
|
ss->fd, rType));
|
|
|
|
(void)SSL3_SendAlert(ss, alert_fatal, illegal_parameter);
|
|
PORT_SetError(SSL_ERROR_RX_UNKNOWN_RECORD_TYPE);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* KeyUpdate in DTLS1.3 is required to be ACKed.
|
|
The dtls13_maybeProcessKeyUpdateAck function is called when we receive a message acknowledging KeyUpdate.
|
|
The function will then update the writing keys of the party started KeyUpdate.
|
|
*/
|
|
SECStatus
|
|
dtls13_maybeProcessKeyUpdateAck(sslSocket *ss, PRUint16 entrySeq)
|
|
{
|
|
/* RFC 9147. Section 8.
|
|
Due to the possibility of an ACK message for a KeyUpdate being lost
|
|
and thereby preventing the sender of KeyUpdate from updating its
|
|
keying material, receivers MUST retain the pre-update keying material
|
|
until receipt and successful decryption of a message using the new
|
|
keys.*/
|
|
|
|
if (ss->ssl3.hs.isKeyUpdateInProgress && entrySeq == ss->ssl3.hs.dtlsHandhakeKeyUpdateMessage) {
|
|
SSL_TRC(30, ("%d: DTLS13[%d]: %s key update is completed", SSL_GETPID(), ss->fd, SSL_ROLE(ss)));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
|
|
SECStatus rv = SECSuccess;
|
|
rv = tls13_UpdateTrafficKeys(ss, ssl_secret_write);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
PORT_Assert(ss->ssl3.hs.isKeyUpdateInProgress);
|
|
ss->ssl3.hs.isKeyUpdateInProgress = PR_FALSE;
|
|
|
|
return rv;
|
|
}
|
|
|
|
else
|
|
return SECSuccess;
|
|
}
|
|
|
|
SECStatus
|
|
dtls13_HandleAck(sslSocket *ss, sslBuffer *databuf)
|
|
{
|
|
PRUint8 *b = databuf->buf;
|
|
PRUint32 l = databuf->len;
|
|
unsigned int length;
|
|
SECStatus rv;
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
|
|
/* Ensure we don't loop. */
|
|
databuf->len = 0;
|
|
|
|
PORT_Assert(IS_DTLS(ss));
|
|
if (!tls13_MaybeTls13(ss)) {
|
|
tls13_FatalError(ss, SSL_ERROR_RX_UNKNOWN_RECORD_TYPE, illegal_parameter);
|
|
return SECFailure;
|
|
}
|
|
|
|
SSL_TRC(10, ("%d: SSL3[%d]: Handling ACK", SSL_GETPID(), ss->fd));
|
|
rv = ssl3_ConsumeHandshakeNumber(ss, &length, 2, &b, &l);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
if (length != l) {
|
|
goto loser;
|
|
}
|
|
|
|
while (l > 0) {
|
|
PRUint64 seq;
|
|
PRUint64 epoch;
|
|
PRCList *cursor;
|
|
|
|
rv = ssl3_ConsumeHandshakeNumber64(ss, &epoch, 8, &b, &l);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
rv = ssl3_ConsumeHandshakeNumber64(ss, &seq, 8, &b, &l);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
if (epoch > RECORD_EPOCH_MAX) {
|
|
SSL_TRC(50, ("%d: SSL3[%d]: ACK message was rejected: the epoch exceeds the limit", SSL_GETPID(), ss->fd));
|
|
continue;
|
|
}
|
|
if (seq > RECORD_SEQ_MAX) {
|
|
SSL_TRC(50, ("%d: SSL3[%d]: ACK message was rejected: the sequence number exceeds the limit", SSL_GETPID(), ss->fd));
|
|
continue;
|
|
}
|
|
|
|
seq = dtls_CombineSequenceNumber(epoch, seq);
|
|
|
|
for (cursor = PR_LIST_HEAD(&ss->ssl3.hs.dtlsSentHandshake);
|
|
cursor != &ss->ssl3.hs.dtlsSentHandshake;
|
|
cursor = PR_NEXT_LINK(cursor)) {
|
|
DTLSHandshakeRecordEntry *entry = (DTLSHandshakeRecordEntry *)cursor;
|
|
|
|
if (entry->record == seq) {
|
|
SSL_TRC(30, (
|
|
"%d: DTLS13[%d]: Marking record=%llx message %d offset %d length=%d as ACKed",
|
|
SSL_GETPID(), ss->fd,
|
|
entry->record, entry->messageSeq, entry->offset, entry->length));
|
|
entry->acked = PR_TRUE;
|
|
|
|
/* When we sent a KeyUpdate message, we have recorded the identifier of the message.
|
|
During the HandleACK we check if we received an ack for the KeyUpdate message we sent.*/
|
|
rv = dtls13_maybeProcessKeyUpdateAck(ss, entry->messageSeq);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Try to flush. */
|
|
rv = dtls_TransmitMessageFlight(ss);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
/* Reset the retransmit timer. */
|
|
if (ss->ssl3.hs.rtTimer->cb) {
|
|
(void)dtls_RestartTimer(ss, ss->ssl3.hs.rtTimer);
|
|
}
|
|
|
|
/* If there are no more messages to send, cleanup. */
|
|
if (PR_CLIST_IS_EMPTY(&ss->ssl3.hs.lastMessageFlight)) {
|
|
SSL_TRC(10, ("%d: SSL3[%d]: No more unacked handshake messages",
|
|
SSL_GETPID(), ss->fd));
|
|
|
|
dtls_CancelTimer(ss, ss->ssl3.hs.rtTimer);
|
|
ssl_ClearPRCList(&ss->ssl3.hs.dtlsSentHandshake, NULL);
|
|
/* If the handshake is finished, and we're the client then
|
|
* also clean up the handshake read cipher spec. Any ACKs
|
|
* we receive will be with the application data cipher spec.
|
|
* The server needs to keep the handshake cipher spec around
|
|
* for the holddown period to process retransmitted Finisheds.
|
|
*/
|
|
if (!ss->sec.isServer && (ss->ssl3.hs.ws == idle_handshake)) {
|
|
ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read,
|
|
TrafficKeyHandshake);
|
|
}
|
|
}
|
|
return SECSuccess;
|
|
|
|
loser:
|
|
/* Due to bug 1829391 we may incorrectly send an alert rather than
|
|
* ignore an invalid record here. */
|
|
SSL_TRC(11, ("%d: SSL3[%d]: Error processing DTLS1.3 ACK.",
|
|
SSL_GETPID(), ss->fd));
|
|
PORT_SetError(SSL_ERROR_RX_MALFORMED_DTLS_ACK);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* Clean up the read timer for the handshake cipher suites on the
|
|
* server.
|
|
*
|
|
* In DTLS 1.3, the client speaks last (Finished), and will retransmit
|
|
* until the server ACKs that message (using application data cipher
|
|
* suites). I.e.,
|
|
*
|
|
* - The client uses the retransmit timer and retransmits using the
|
|
* saved write handshake cipher suite.
|
|
* - The server keeps the saved read handshake cipher suite around
|
|
* for the holddown period in case it needs to read the Finished.
|
|
*
|
|
* After the holddown period, the server assumes the client is happy
|
|
* and discards the handshake read cipher suite.
|
|
*/
|
|
|
|
void
|
|
dtls13_HolddownTimerCb(sslSocket *ss)
|
|
{
|
|
SSL_TRC(10, ("%d: SSL3[%d]: holddown timer fired",
|
|
SSL_GETPID(), ss->fd));
|
|
ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyHandshake);
|
|
ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL);
|
|
}
|
|
|
|
/*RFC 9147. 4.2.3. Record Number Encryption*/
|
|
SECStatus
|
|
dtls13_MaskSequenceNumber(sslSocket *ss, ssl3CipherSpec *spec,
|
|
PRUint8 *hdr, PRUint8 *cipherText, PRUint32 cipherTextLen)
|
|
{
|
|
PORT_Assert(IS_DTLS(ss));
|
|
if (spec->version < SSL_LIBRARY_VERSION_TLS_1_3) {
|
|
return SECSuccess;
|
|
}
|
|
|
|
if (spec->maskContext) {
|
|
#ifdef UNSAFE_FUZZER_MODE
|
|
/* Use a null mask. */
|
|
PRUint8 mask[2] = { 0 };
|
|
#else
|
|
/* "This procedure requires the ciphertext length be at least 16 bytes.
|
|
* Receivers MUST reject shorter records as if they had failed
|
|
* deprotection, as described in Section 4.5.2." */
|
|
if (cipherTextLen < 16) {
|
|
PORT_SetError(SSL_ERROR_BAD_MAC_READ);
|
|
return SECFailure;
|
|
}
|
|
|
|
PRUint8 mask[2];
|
|
SECStatus rv = ssl_CreateMaskInner(spec->maskContext, cipherText, cipherTextLen, mask, sizeof(mask));
|
|
|
|
if (rv != SECSuccess) {
|
|
PORT_SetError(SSL_ERROR_BAD_MAC_READ);
|
|
return SECFailure;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
The encrypted sequence number is computed by XORing the leading bytes
|
|
of the mask with the on-the-wire representation of the sequence
|
|
number. Decryption is accomplished by the same process.
|
|
*/
|
|
|
|
PRUint32 maskSBitIsSet = 0x08;
|
|
hdr[1] ^= mask[0];
|
|
if (hdr[0] & maskSBitIsSet) {
|
|
hdr[2] ^= mask[1];
|
|
}
|
|
}
|
|
return SECSuccess;
|
|
}
|
|
|
|
CK_MECHANISM_TYPE
|
|
tls13_SequenceNumberEncryptionMechanism(SSLCipherAlgorithm bulkAlgorithm)
|
|
{
|
|
/*
|
|
When the AEAD is based on AES, then the mask is generated by
|
|
computing AES-ECB on the first 16 bytes of the ciphertext:
|
|
|
|
When the AEAD is based on ChaCha20, then the mask is generated by
|
|
treating the first 4 bytes of the ciphertext as the block counter and
|
|
the next 12 bytes as the nonce, passing them to the ChaCha20 block
|
|
function.
|
|
*/
|
|
|
|
switch (bulkAlgorithm) {
|
|
case ssl_calg_aes_gcm:
|
|
return CKM_AES_ECB;
|
|
case ssl_calg_chacha20:
|
|
return CKM_NSS_CHACHA20_CTR;
|
|
default:
|
|
PORT_Assert(PR_FALSE);
|
|
}
|
|
return CKM_INVALID_MECHANISM;
|
|
}
|
|
|
|
/* The function constucts the KeyUpdate Message.
|
|
The structure is presented in RFC 9147 Section 5.2. */
|
|
|
|
SECStatus
|
|
dtls13_EnqueueKeyUpdateMessage(sslSocket *ss, tls13KeyUpdateRequest request)
|
|
{
|
|
SECStatus rv = SECFailure;
|
|
/*
|
|
The epoch number is initially zero and is incremented each time
|
|
keying material changes and a sender aims to rekey.
|
|
More details are provided in RFC 9147 Section 6.1.*/
|
|
rv = ssl3_AppendHandshakeHeaderAndStashSeqNum(ss, ssl_hs_key_update, 1, &ss->ssl3.hs.dtlsHandhakeKeyUpdateMessage);
|
|
if (rv != SECSuccess) {
|
|
return rv; /* error code set by ssl3_AppendHandshakeHeader, if applicable. */
|
|
}
|
|
rv = ssl3_AppendHandshakeNumber(ss, request, 1);
|
|
if (rv != SECSuccess) {
|
|
return rv; /* error code set by ssl3_AppendHandshakeNumber, if applicable. */
|
|
}
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* The ssl3CipherSpecStr (sslspec.h) structure describes a spec for r/w records.
|
|
For the specification, the epoch is defined as uint16 value,
|
|
So the maximum epoch is 2 ^ 16 - 1*/
|
|
#define DTLS13_MAX_EPOCH_TYPE PR_UINT16_MAX
|
|
/*RFC 9147. Section 8.
|
|
In order to provide an extra margin of security,
|
|
sending implementations MUST NOT allow the epoch to exceed 2^48-1.*/
|
|
#define DTLS13_MAX_EPOCH ((0x1ULL << 48) - 1)
|
|
|
|
SECStatus
|
|
dtls13_MaybeSendKeyUpdate(sslSocket *ss, tls13KeyUpdateRequest request, PRBool buffer)
|
|
{
|
|
|
|
SSL_TRC(30, ("%d: DTLS13[%d]: %s sends key update, response %s",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss),
|
|
(request == update_requested) ? "requested"
|
|
: "not requested"));
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
|
|
SECStatus rv = SECFailure;
|
|
/*
|
|
For the specification, the epoch is defined as uint16 value (see bug 1809872)
|
|
and the sendKeyUpdate will update the writing keys
|
|
so, if the epoch is already maximum, KeyUpdate will be cancelled.*/
|
|
|
|
ssl_GetSpecWriteLock(ss);
|
|
/* This check is done as well in the updateTrafficKey function */
|
|
if (ss->ssl3.cwSpec->epoch >= DTLS13_MAX_EPOCH_TYPE) {
|
|
ssl_ReleaseSpecWriteLock(ss);
|
|
SSL_TRC(30, ("%d: DTLS13[%d]: %s keyUpdate request was cancelled, as the writing epoch arrived to the maximum possible",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss)));
|
|
PORT_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED);
|
|
return SECFailure;
|
|
} else {
|
|
ssl_ReleaseSpecWriteLock(ss);
|
|
}
|
|
|
|
PORT_Assert(DTLS13_MAX_EPOCH_TYPE <= DTLS13_MAX_EPOCH);
|
|
|
|
ssl_GetSpecReadLock(ss);
|
|
/* TODO(AW) - See bug 1809872. */
|
|
if (request == update_requested && ss->ssl3.crSpec->epoch >= DTLS13_MAX_EPOCH_TYPE) {
|
|
SSL_TRC(30, ("%d: DTLS13[%d]: %s keyUpdate request update_requested was cancelled, as the reading epoch arrived to the maximum possible",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss)));
|
|
request = update_not_requested;
|
|
}
|
|
ssl_ReleaseSpecReadLock(ss);
|
|
|
|
/* RFC 9147. Section 5.8.4.
|
|
In contrast, implementations MUST NOT send KeyUpdate, NewConnectionId, or
|
|
RequestConnectionId messages if an earlier message of the same type
|
|
has not yet been acknowledged.*/
|
|
if (ss->ssl3.hs.isKeyUpdateInProgress) {
|
|
SSL_TRC(30, ("%d: DTLS13[%d]: the previous %s KeyUpdate message was not yet ack-ed, dropping",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss), ss->ssl3.hs.sendMessageSeq));
|
|
return SECSuccess;
|
|
}
|
|
|
|
ssl_GetXmitBufLock(ss);
|
|
rv = dtls13_EnqueueKeyUpdateMessage(ss, request);
|
|
if (rv != SECSuccess) {
|
|
return rv; /* error code already set */
|
|
}
|
|
|
|
/* Trying to send the message - without buffering. */
|
|
/* TODO[AW]: As I just emulated the API, I am not sure that it's necessary to buffer. */
|
|
rv = ssl3_FlushHandshake(ss, 0);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure; /* error code set by ssl3_FlushHandshake */
|
|
}
|
|
ssl_ReleaseXmitBufLock(ss);
|
|
|
|
/* The keyUpdate is started. */
|
|
PORT_Assert(ss->ssl3.hs.isKeyUpdateInProgress == PR_FALSE);
|
|
ss->ssl3.hs.isKeyUpdateInProgress = PR_TRUE;
|
|
|
|
SSL_TRC(30, ("%d: DTLS13[%d]: %s has just sent keyUpdate request #%d and is waiting for ack",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss), ss->ssl3.hs.dtlsHandhakeKeyUpdateMessage));
|
|
return SECSuccess;
|
|
}
|
|
|
|
SECStatus
|
|
dtls13_HandleKeyUpdate(sslSocket *ss, PRUint8 *b, unsigned int length, PRBool update)
|
|
{
|
|
SSL_TRC(10, ("%d: DTLS13[%d]: %s handles Key Update",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss)));
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
SECStatus rv = SECSuccess;
|
|
if (update == update_requested) {
|
|
/* Respond immediately (don't buffer). */
|
|
rv = tls13_SendKeyUpdate(ss, update_not_requested, PR_FALSE);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure; /* Error already set. */
|
|
}
|
|
}
|
|
|
|
SSL_TRC(30, ("%d: DTLS13[%d]: now %s is allowing the messages from the previous epoch",
|
|
SSL_GETPID(), ss->fd, SSL_ROLE(ss)));
|
|
ss->ssl3.hs.allowPreviousEpoch = PR_TRUE;
|
|
/* Updating the reading key. */
|
|
rv = tls13_UpdateTrafficKeys(ss, ssl_secret_read);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure; /* Error code set by tls13_UpdateTrafficKeys. */
|
|
}
|
|
return SECSuccess;
|
|
} |