DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (b6d82b1a6b02)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * Anti-replay measures for TLS 1.3.
 *
 * 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/. */

#include "nss.h"      /* for NSS_RegisterShutdown */
#include "nssilock.h" /* for PZMonitor */
#include "pk11pub.h"
#include "prmon.h"
#include "prtime.h"
#include "secerr.h"
#include "ssl.h"
#include "sslbloom.h"
#include "sslimpl.h"
#include "tls13hkdf.h"

struct SSLAntiReplayContextStr {
    /* The number of outstanding references to this context. */
    PRInt32 refCount;
    /* Used to serialize access. */
    PZMonitor *lock;
    /* The filters, use of which alternates. */
    sslBloomFilter filters[2];
    /* Which of the two filters is active (0 or 1). */
    PRUint8 current;
    /* The time that we will next update. */
    PRTime nextUpdate;
    /* The width of the window; i.e., the period of updates. */
    PRTime window;
    /* This key ensures that the bloom filter index is unpredictable. */
    PK11SymKey *key;
};

void
tls13_ReleaseAntiReplayContext(SSLAntiReplayContext *ctx)
{
    if (!ctx) {
        return;
    }
    if (PR_ATOMIC_DECREMENT(&ctx->refCount) >= 1) {
        return;
    }

    if (ctx->lock) {
        PZ_DestroyMonitor(ctx->lock);
        ctx->lock = NULL;
    }
    PK11_FreeSymKey(ctx->key);
    ctx->key = NULL;
    sslBloom_Destroy(&ctx->filters[0]);
    sslBloom_Destroy(&ctx->filters[1]);
    PORT_Free(ctx);
}

/* Clear the current state and free any resources we allocated. The signature
 * here is odd to allow this to be called during shutdown. */
SECStatus
SSLExp_ReleaseAntiReplayContext(SSLAntiReplayContext *ctx)
{
    tls13_ReleaseAntiReplayContext(ctx);
    return SECSuccess;
}

SSLAntiReplayContext *
tls13_RefAntiReplayContext(SSLAntiReplayContext *ctx)
{
    PORT_Assert(ctx);
    PR_ATOMIC_INCREMENT(&ctx->refCount);
    return ctx;
}

static SECStatus
tls13_AntiReplayKeyGen(SSLAntiReplayContext *ctx)
{
    PRUint8 buf[32];
    SECItem keyItem = { siBuffer, buf, sizeof(buf) };
    PK11SlotInfo *slot;
    SECStatus rv;

    PORT_Assert(ctx);

    slot = PK11_GetInternalSlot();
    if (!slot) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }
    rv = PK11_GenerateRandomOnSlot(slot, buf, sizeof(buf));
    if (rv != SECSuccess) {
        goto loser;
    }

    ctx->key = PK11_ImportSymKey(slot, CKM_NSS_HKDF_SHA256,
                                 PK11_OriginUnwrap, CKA_DERIVE,
                                 &keyItem, NULL);
    if (!ctx->key) {
        goto loser;
    }

    PK11_FreeSlot(slot);
    return SECSuccess;

loser:
    PK11_FreeSlot(slot);
    return SECFailure;
}

/* Set a limit on the combination of number of hashes and bits in each hash. */
#define SSL_MAX_BLOOM_FILTER_SIZE 64

/*
 * The context created by this function can be called concurrently on multiple
 * threads if the server is multi-threaded.  A monitor is used to ensure that
 * only one thread can access the structures that change over time, but no such
 * guarantee is provided for configuration data.
 */
SECStatus
SSLExp_CreateAntiReplayContext(PRTime now, PRTime window, unsigned int k,
                               unsigned int bits, SSLAntiReplayContext **pctx)
{
    SECStatus rv;

    if (window <= 0 || k == 0 || bits == 0 || pctx == NULL) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if ((k * (bits + 7) / 8) > SSL_MAX_BLOOM_FILTER_SIZE) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    SSLAntiReplayContext *ctx = PORT_ZNew(SSLAntiReplayContext);
    if (!ctx) {
        return SECFailure; /* Code already set. */
    }

    ctx->refCount = 1;
    ctx->lock = PZ_NewMonitor(nssILockSSL);
    if (!ctx->lock) {
        goto loser; /* Code already set. */
    }

    rv = tls13_AntiReplayKeyGen(ctx);
    if (rv != SECSuccess) {
        goto loser; /* Code already set. */
    }

    rv = sslBloom_Init(&ctx->filters[0], k, bits);
    if (rv != SECSuccess) {
        goto loser; /* Code already set. */
    }
    rv = sslBloom_Init(&ctx->filters[1], k, bits);
    if (rv != SECSuccess) {
        goto loser; /* Code already set. */
    }
    /* When starting out, ensure that 0-RTT is not accepted until the window is
     * updated.  A ClientHello might have been accepted prior to a restart. */
    sslBloom_Fill(&ctx->filters[1]);

    ctx->current = 0;
    ctx->nextUpdate = now + window;
    ctx->window = window;
    *pctx = ctx;
    return SECSuccess;

loser:
    tls13_ReleaseAntiReplayContext(ctx);
    return SECFailure;
}

SECStatus
SSLExp_SetAntiReplayContext(PRFileDesc *fd, SSLAntiReplayContext *ctx)
{
    sslSocket *ss = ssl_FindSocket(fd);
    if (!ss) {
        return SECFailure; /* Code already set. */
    }
    tls13_ReleaseAntiReplayContext(ss->antiReplay);
    if (ctx != NULL) {
        ss->antiReplay = tls13_RefAntiReplayContext(ctx);
    } else {
        ss->antiReplay = NULL;
    }
    return SECSuccess;
}

static void
tls13_AntiReplayUpdate(SSLAntiReplayContext *ctx, PRTime now)
{
    PR_ASSERT_CURRENT_THREAD_IN_MONITOR(ctx->lock);
    if (now >= ctx->nextUpdate) {
        ctx->current ^= 1;
        ctx->nextUpdate = now + ctx->window;
        sslBloom_Zero(ctx->filters + ctx->current);
    }
}

PRBool
tls13_InWindow(const sslSocket *ss, const sslSessionID *sid)
{
    PRInt32 timeDelta;

    /* Calculate the difference between the client's view of the age of the
     * ticket (in |ss->xtnData.ticketAge|) and the server's view, which we now
     * calculate.  The result should be close to zero.  timeDelta is signed to
     * make the comparisons below easier. */
    timeDelta = ss->xtnData.ticketAge -
                ((ssl_Time(ss) - sid->creationTime) / PR_USEC_PER_MSEC);

    /* Only allow the time delta to be at most half of our window.  This is
     * symmetrical, though it doesn't need to be; this assumes that clock errors
     * on server and client will tend to cancel each other out.
     *
     * There are two anti-replay filters that roll over each window.  In the
     * worst case, immediately after a rollover of the filters, we only have a
     * single window worth of recorded 0-RTT attempts.  Thus, the period in
     * which we can accept 0-RTT is at most one window wide.  This uses PR_ABS()
     * and half the window so that the first attempt can be up to half a window
     * early and then replays will be caught until the attempts are half a
     * window late.
     *
     * For example, a 0-RTT attempt arrives early, but near the end of window 1.
     * The attempt is then recorded in window 1.  Rollover to window 2 could
     * occur immediately afterwards.  Window 1 is still checked for new 0-RTT
     * attempts for the remainder of window 2.  Therefore, attempts to replay
     * are detected because the value is recorded in window 1.  When rollover
     * occurs again, window 1 is erased and window 3 instated.  If we allowed an
     * attempt to be late by more than half a window, then this check would not
     * prevent the same 0-RTT attempt from being accepted during window 1 and
     * later window 3.
     */
    PRInt32 allowance = ss->antiReplay->window / (PR_USEC_PER_MSEC * 2);
    SSL_TRC(10, ("%d: TLS13[%d]: replay check time delta=%d, allow=%d",
                 SSL_GETPID(), ss->fd, timeDelta, allowance));
    return PR_ABS(timeDelta) < allowance;
}

/* Checks for a duplicate in the two filters we have.  Performs maintenance on
 * the filters as a side-effect. This only detects a probable replay, it's
 * possible that this will return true when the 0-RTT attempt is not genuinely a
 * replay.  In that case, we reject 0-RTT unnecessarily, but that's OK because
 * no client expects 0-RTT to work every time. */
PRBool
tls13_IsReplay(const sslSocket *ss, const sslSessionID *sid)
{
    PRBool replay;
    unsigned int size;
    PRUint8 index;
    SECStatus rv;
    static const char *label = "anti-replay";
    PRUint8 buf[SSL_MAX_BLOOM_FILTER_SIZE];
    SSLAntiReplayContext *ctx = ss->antiReplay;

    /* If SSL_SetAntiReplayContext hasn't been called with a valid context, then
     * treat all attempts at 0-RTT as a replay. */
    if (ctx == NULL) {
        return PR_TRUE;
    }

    if (!tls13_InWindow(ss, sid)) {
        return PR_TRUE;
    }

    size = ctx->filters[0].k * (ctx->filters[0].bits + 7) / 8;
    PORT_Assert(size <= SSL_MAX_BLOOM_FILTER_SIZE);
    rv = tls13_HkdfExpandLabelRaw(ctx->key, ssl_hash_sha256,
                                  ss->xtnData.pskBinder.data,
                                  ss->xtnData.pskBinder.len,
                                  label, strlen(label),
                                  buf, size);
    if (rv != SECSuccess) {
        return PR_TRUE;
    }

    PZ_EnterMonitor(ctx->lock);
    tls13_AntiReplayUpdate(ctx, ssl_Time(ss));

    index = ctx->current;
    replay = sslBloom_Add(&ctx->filters[index], buf);
    SSL_TRC(10, ("%d: TLS13[%d]: replay check current window: %s",
                 SSL_GETPID(), ss->fd, replay ? "replay" : "ok"));
    if (!replay) {
        replay = sslBloom_Check(&ctx->filters[index ^ 1], buf);
        SSL_TRC(10, ("%d: TLS13[%d]: replay check previous window: %s",
                     SSL_GETPID(), ss->fd, replay ? "replay" : "ok"));
    }

    PZ_ExitMonitor(ctx->lock);
    return replay;
}