/*
* Copyright (C) 2016 Red Hat, Inc.
*
* Authors: Fridolin Pokorny
* Nikos Mavrogiannopoulos
*
* This file is part of GNUTLS.
*
* The GNUTLS library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
*
*/
/* Functions that relate to DTLS sliding window handling.
*/
#ifndef DTLS_SW_NO_INCLUDES
#include "gnutls_int.h"
#include "errors.h"
#include "debug.h"
#include "dtls.h"
#include "record.h"
#endif
/*
* DTLS sliding window handling
*/
#define DTLS_EPOCH_SHIFT (6*CHAR_BIT)
#define DTLS_SEQ_NUM_MASK 0x0000FFFFFFFFFFFF
#define DTLS_EMPTY_BITMAP (0xFFFFFFFFFFFFFFFFULL)
/* We expect the compiler to be able to spot that this is a byteswapping
* load, and emit instructions like 'movbe' on x86_64 where appropriate.
*/
#define LOAD_UINT64(out, ubytes) \
out = (((uint64_t)ubytes[0] << 56) | \
((uint64_t)ubytes[1] << 48) | \
((uint64_t)ubytes[2] << 40) | \
((uint64_t)ubytes[3] << 32) | \
((uint64_t)ubytes[4] << 24) | \
((uint64_t)ubytes[5] << 16) | \
((uint64_t)ubytes[6] << 8) | \
((uint64_t)ubytes[7] << 0) )
void _dtls_reset_window(struct record_parameters_st *rp)
{
rp->dtls_sw_have_recv = 0;
}
/* Checks if a sequence number is not replayed. If a replayed
* packet is detected it returns a negative value (but no sensible error code).
* Otherwise zero.
*/
int _dtls_record_check(struct record_parameters_st *rp, gnutls_uint64 * _seq)
{
uint64_t seq_num = 0;
LOAD_UINT64(seq_num, _seq->i);
if ((seq_num >> DTLS_EPOCH_SHIFT) != rp->epoch) {
return gnutls_assert_val(-1);
}
seq_num &= DTLS_SEQ_NUM_MASK;
/*
* rp->dtls_sw_next is the next *expected* packet (N), being
* the sequence number *after* the latest we have received.
*
* By definition, therefore, packet N-1 *has* been received.
* And thus there's no point wasting a bit in the bitmap for it.
*
* So the backlog bitmap covers the 64 packets prior to that,
* with the LSB representing packet (N - 2), and the MSB
* representing (N - 65). A received packet is represented
* by a zero bit, and a missing packet is represented by a one.
*
* Thus we can allow out-of-order reception of packets that are
* within a reasonable interval of the latest packet received.
*/
if (!rp->dtls_sw_have_recv) {
rp->dtls_sw_next = seq_num + 1;
rp->dtls_sw_bits = DTLS_EMPTY_BITMAP;
rp->dtls_sw_have_recv = 1;
return 0;
} else if (seq_num == rp->dtls_sw_next) {
/* The common case. This is the packet we expected next. */
rp->dtls_sw_bits <<= 1;
/* This might reach a value higher than 48-bit DTLS sequence
* numbers can actually reach. Which is fine. When that
* happens, we'll do the right thing and just not accept
* any newer packets. Someone needs to start a new epoch. */
rp->dtls_sw_next++;
return 0;
} else if (seq_num > rp->dtls_sw_next) {
/* The packet we were expecting has gone missing; this one is newer.
* We always advance the window to accommodate it. */
uint64_t delta = seq_num - rp->dtls_sw_next;
if (delta >= 64) {
/* We jumped a long way into the future. We have not seen
* any of the previous 32 packets so set the backlog bitmap
* to all ones. */
rp->dtls_sw_bits = DTLS_EMPTY_BITMAP;
} else if (delta == 63) {
/* Avoid undefined behaviour that shifting by 64 would incur.
* The (clear) top bit represents the packet which is currently
* rp->dtls_sw_next, which we know was already received. */
rp->dtls_sw_bits = DTLS_EMPTY_BITMAP >> 1;
} else {
/* We have missed (delta) packets. Shift the backlog by that
* amount *plus* the one we would have shifted it anyway if
* we'd received the packet we were expecting. The zero bit
* representing the packet which is currently rp->dtls_sw_next-1,
* which we know has been received, ends up at bit position
* (1<dtls_sw_bits <<= delta + 1;
rp->dtls_sw_bits |= (1ULL << delta) - 1;
}
rp->dtls_sw_next = seq_num + 1;
return 0;
} else {
/* This packet is older than the one we were expecting. By how much...? */
uint64_t delta = rp->dtls_sw_next - seq_num;
if (delta > 65) {
/* Too old. We can't know if it's a replay */
return gnutls_assert_val(-2);
} else if (delta == 1) {
/* Not in the bitmask since it is by definition already received. */
return gnutls_assert_val(-3);
} else {
/* Within the sliding window, so we remember whether we've seen it or not */
uint64_t mask = 1ULL << (rp->dtls_sw_next - seq_num - 2);
if (!(rp->dtls_sw_bits & mask))
return gnutls_assert_val(-3);
rp->dtls_sw_bits &= ~mask;
return 0;
}
}
}