Home | History | Annotate | Download | only in crypto
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/quic/crypto/strike_register.h"
      6 
      7 #include <set>
      8 #include <string>
      9 
     10 #include "base/basictypes.h"
     11 #include "testing/gtest/include/gtest/gtest.h"
     12 
     13 namespace {
     14 
     15 using net::StrikeRegister;
     16 using std::set;
     17 using std::string;
     18 
     19 const uint8 kOrbit[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
     20 
     21 // StrikeRegisterTests don't look at the random bytes so this function can
     22 // simply set the random bytes to 0.
     23 void SetNonce(uint8 nonce[32], unsigned time, const uint8 orbit[8]) {
     24   nonce[0] = time >> 24;
     25   nonce[1] = time >> 16;
     26   nonce[2] = time >> 8;
     27   nonce[3] = time;
     28   memcpy(nonce + 4, orbit, 8);
     29   memset(nonce + 12, 0, 20);
     30 }
     31 
     32 TEST(StrikeRegisterTest, SimpleHorizon) {
     33   // The set must reject values created on or before its own creation time.
     34   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     35                      100 /* window secs */, kOrbit,
     36                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
     37   uint8 nonce[32];
     38   SetNonce(nonce, 999, kOrbit);
     39   ASSERT_FALSE(set.Insert(nonce, 1000));
     40   SetNonce(nonce, 1000, kOrbit);
     41   ASSERT_FALSE(set.Insert(nonce, 1000));
     42 }
     43 
     44 TEST(StrikeRegisterTest, NoStartupMode) {
     45   // Check that a strike register works immediately if NO_STARTUP_PERIOD_NEEDED
     46   // is specified.
     47   StrikeRegister set(10 /* max size */, 0 /* current time */,
     48                      100 /* window secs */, kOrbit,
     49                      StrikeRegister::NO_STARTUP_PERIOD_NEEDED);
     50   uint8 nonce[32];
     51   SetNonce(nonce, 0, kOrbit);
     52   ASSERT_TRUE(set.Insert(nonce, 0));
     53   ASSERT_FALSE(set.Insert(nonce, 0));
     54 }
     55 
     56 TEST(StrikeRegisterTest, WindowFuture) {
     57   // The set must reject values outside the window.
     58   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     59                      100 /* window secs */, kOrbit,
     60                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
     61   uint8 nonce[32];
     62   SetNonce(nonce, 1101, kOrbit);
     63   ASSERT_FALSE(set.Insert(nonce, 1000));
     64   SetNonce(nonce, 999, kOrbit);
     65   ASSERT_FALSE(set.Insert(nonce, 1100));
     66 }
     67 
     68 TEST(StrikeRegisterTest, BadOrbit) {
     69   // The set must reject values with the wrong orbit
     70   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     71                      100 /* window secs */, kOrbit,
     72                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
     73   uint8 nonce[32];
     74   static const uint8 kBadOrbit[8] = { 0, 0, 0, 0, 1, 1, 1, 1 };
     75   SetNonce(nonce, 1101, kBadOrbit);
     76   ASSERT_FALSE(set.Insert(nonce, 1100));
     77 }
     78 
     79 TEST(StrikeRegisterTest, OneValue) {
     80   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     81                      100 /* window secs */, kOrbit,
     82                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
     83   uint8 nonce[32];
     84   SetNonce(nonce, 1101, kOrbit);
     85   ASSERT_TRUE(set.Insert(nonce, 1100));
     86 }
     87 
     88 TEST(StrikeRegisterTest, RejectDuplicate) {
     89   // The set must reject values with the wrong orbit
     90   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     91                      100 /* window secs */, kOrbit,
     92                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
     93   uint8 nonce[32];
     94   SetNonce(nonce, 1101, kOrbit);
     95   ASSERT_TRUE(set.Insert(nonce, 1100));
     96   ASSERT_FALSE(set.Insert(nonce, 1100));
     97 }
     98 
     99 TEST(StrikeRegisterTest, HorizonUpdating) {
    100   StrikeRegister set(5 /* max size */, 1000 /* current time */,
    101                      100 /* window secs */, kOrbit,
    102                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
    103   uint8 nonce[6][32];
    104   for (unsigned i = 0; i < 5; i++) {
    105     SetNonce(nonce[i], 1101, kOrbit);
    106     nonce[i][31] = i;
    107     ASSERT_TRUE(set.Insert(nonce[i], 1100));
    108   }
    109 
    110   // This should push the oldest value out and force the horizon to be updated.
    111   SetNonce(nonce[5], 1102, kOrbit);
    112   ASSERT_TRUE(set.Insert(nonce[5], 1100));
    113 
    114   // This should be behind the horizon now:
    115   SetNonce(nonce[5], 1101, kOrbit);
    116   nonce[5][31] = 10;
    117   ASSERT_FALSE(set.Insert(nonce[5], 1100));
    118 }
    119 
    120 TEST(StrikeRegisterTest, InsertMany) {
    121   StrikeRegister set(5000 /* max size */, 1000 /* current time */,
    122                      500 /* window secs */, kOrbit,
    123                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
    124 
    125   uint8 nonce[32];
    126   SetNonce(nonce, 1101, kOrbit);
    127   for (unsigned i = 0; i < 100000; i++) {
    128     SetNonce(nonce, 1101 + i/500, kOrbit);
    129     memcpy(nonce + 12, &i, sizeof(i));
    130     set.Insert(nonce, 1100);
    131   }
    132 }
    133 
    134 
    135 // For the following test we create a slow, but simple, version of a
    136 // StrikeRegister. The behaviour of this object is much easier to understand
    137 // than the fully fledged version. We then create a test to show, empirically,
    138 // that the two objects have identical behaviour.
    139 
    140 // A SlowStrikeRegister has the same public interface as a StrikeRegister, but
    141 // is much slower. Hopefully it is also more obviously correct and we can
    142 // empirically test that their behaviours are identical.
    143 class SlowStrikeRegister {
    144  public:
    145   SlowStrikeRegister(unsigned max_entries, uint32 current_time,
    146                      uint32 window_secs, const uint8 orbit[8])
    147       : max_entries_(max_entries),
    148         window_secs_(window_secs),
    149         creation_time_(current_time),
    150         horizon_(ExternalTimeToInternal(current_time + window_secs)) {
    151     memcpy(orbit_, orbit, sizeof(orbit_));
    152   }
    153 
    154   bool Insert(const uint8 nonce_bytes[32], const uint32 current_time_external) {
    155     const uint32 current_time = ExternalTimeToInternal(current_time_external);
    156 
    157     // Check to see if the orbit is correct.
    158     if (memcmp(nonce_bytes + 4, orbit_, sizeof(orbit_))) {
    159       return false;
    160     }
    161     const uint32 nonce_time =
    162         ExternalTimeToInternal(TimeFromBytes(nonce_bytes));
    163     // We have dropped one or more nonces with a time value of |horizon_|, so
    164     // we have to reject anything with a timestamp less than or equal to that.
    165     if (nonce_time <= horizon_) {
    166       return false;
    167     }
    168 
    169     // Check that the timestamp is in the current window.
    170     if ((current_time > window_secs_ &&
    171          nonce_time < (current_time - window_secs_)) ||
    172         nonce_time > (current_time + window_secs_)) {
    173       return false;
    174     }
    175 
    176     string nonce;
    177     nonce.reserve(32);
    178     nonce +=
    179         string(reinterpret_cast<const char*>(&nonce_time), sizeof(nonce_time));
    180     nonce +=
    181         string(reinterpret_cast<const char*>(nonce_bytes) + sizeof(nonce_time),
    182                32 - sizeof(nonce_time));
    183 
    184     set<string>::const_iterator it = nonces_.find(nonce);
    185     if (it != nonces_.end()) {
    186       return false;
    187     }
    188 
    189     if (nonces_.size() == max_entries_) {
    190       DropOldestEntry();
    191     }
    192 
    193     nonces_.insert(nonce);
    194     return true;
    195   }
    196 
    197  private:
    198   // TimeFromBytes returns a big-endian uint32 from |d|.
    199   static uint32 TimeFromBytes(const uint8 d[4]) {
    200     return static_cast<uint32>(d[0]) << 24 |
    201            static_cast<uint32>(d[1]) << 16 |
    202            static_cast<uint32>(d[2]) << 8 |
    203            static_cast<uint32>(d[3]);
    204   }
    205 
    206   uint32 ExternalTimeToInternal(uint32 external_time) {
    207     static const uint32 kCreationTimeFromInternalEpoch = 63115200.0;
    208     uint32 internal_epoch = 0;
    209     if (creation_time_ > kCreationTimeFromInternalEpoch) {
    210       internal_epoch = creation_time_ - kCreationTimeFromInternalEpoch;
    211     }
    212 
    213     return external_time - internal_epoch;
    214   }
    215 
    216   void DropOldestEntry() {
    217     set<string>::iterator oldest = nonces_.begin(), it;
    218     uint32 oldest_time =
    219         TimeFromBytes(reinterpret_cast<const uint8*>(oldest->data()));
    220 
    221     for (it = oldest; it != nonces_.end(); it++) {
    222       uint32 t = TimeFromBytes(reinterpret_cast<const uint8*>(it->data()));
    223       if (t < oldest_time ||
    224           (t == oldest_time && memcmp(it->data(), oldest->data(), 32) < 0)) {
    225         oldest_time = t;
    226         oldest = it;
    227       }
    228     }
    229 
    230     nonces_.erase(oldest);
    231     horizon_ = oldest_time;
    232   }
    233 
    234   const unsigned max_entries_;
    235   const unsigned window_secs_;
    236   const uint32 creation_time_;
    237   uint8 orbit_[8];
    238   uint32 horizon_;
    239 
    240   set<string> nonces_;
    241 };
    242 
    243 TEST(StrikeRegisterStressTest, Stress) {
    244   // Fixed seed gives reproducibility for this test.
    245   srand(42);
    246   unsigned max_entries = 64;
    247   uint32 current_time = 10000, window = 200;
    248   scoped_ptr<StrikeRegister> s1(
    249       new StrikeRegister(max_entries, current_time, window, kOrbit,
    250                          StrikeRegister::DENY_REQUESTS_AT_STARTUP));
    251   scoped_ptr<SlowStrikeRegister> s2(
    252       new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
    253   uint64 i;
    254 
    255   // When making changes it's worth removing the limit on this test and running
    256   // it for a while. For the initial development an opt binary was left running
    257   // for 10 minutes.
    258   const uint64 kMaxIterations = 10000;
    259   for (i = 0; i < kMaxIterations; i++) {
    260     if (rand() % 1000 == 0) {
    261       // 0.1% chance of resetting the sets.
    262       max_entries = rand() % 300 + 2;
    263       current_time = rand() % 10000;
    264       window = rand() % 500;
    265       s1.reset(new StrikeRegister(max_entries, current_time, window, kOrbit,
    266                                   StrikeRegister::DENY_REQUESTS_AT_STARTUP));
    267       s2.reset(
    268           new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
    269     }
    270 
    271     int32 time_delta = rand() % (window * 4);
    272     time_delta -= window * 2;
    273     const uint32 time = current_time + time_delta;
    274     if (time_delta < 0 && time > current_time) {
    275       continue;  // overflow
    276     }
    277 
    278     uint8 nonce[32];
    279     SetNonce(nonce, time, kOrbit);
    280 
    281     // There are 2048 possible nonce values:
    282     const uint32 v = rand() % 2048;
    283     nonce[30] = v >> 8;
    284     nonce[31] = v;
    285 
    286     const bool r2 = s2->Insert(nonce, time);
    287     const bool r1 = s1->Insert(nonce, time);
    288 
    289     if (r1 != r2) {
    290       break;
    291     }
    292 
    293     if (i % 10 == 0) {
    294       s1->Validate();
    295     }
    296   }
    297 
    298   if (i != kMaxIterations) {
    299     FAIL() << "Failed after " << i << " iterations";
    300   }
    301 }
    302 
    303 }  // anonymous namespace
    304