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 "base/rand_util.h"
     12 #include "testing/gtest/include/gtest/gtest.h"
     13 
     14 namespace {
     15 
     16 using net::InsertStatus;
     17 using net::StrikeRegister;
     18 using std::make_pair;
     19 using std::min;
     20 using std::pair;
     21 using std::set;
     22 using std::string;
     23 
     24 const uint8 kOrbit[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
     25 
     26 // StrikeRegisterTests don't look at the random bytes so this function can
     27 // simply set the random bytes to 0.
     28 void SetNonce(uint8 nonce[32], unsigned time, const uint8 orbit[8]) {
     29   nonce[0] = time >> 24;
     30   nonce[1] = time >> 16;
     31   nonce[2] = time >> 8;
     32   nonce[3] = time;
     33   memcpy(nonce + 4, orbit, 8);
     34   memset(nonce + 12, 0, 20);
     35 }
     36 
     37 TEST(StrikeRegisterTest, SimpleHorizon) {
     38   // The set must reject values created on or before its own creation time.
     39   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     40                      100 /* window secs */, kOrbit,
     41                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
     42   uint8 nonce[32];
     43   SetNonce(nonce, 999, kOrbit);
     44   EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1000));
     45   SetNonce(nonce, 1000, kOrbit);
     46   EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1000));
     47 
     48   EXPECT_EQ(0u, set.GetCurrentValidWindowSecs(1000 /* current time */));
     49   EXPECT_EQ(0u, set.GetCurrentValidWindowSecs(1100 /* current time */));
     50   EXPECT_EQ(1u, set.GetCurrentValidWindowSecs(1101 /* current time */));
     51   EXPECT_EQ(50u, set.GetCurrentValidWindowSecs(1150 /* current time */));
     52   EXPECT_EQ(100u, set.GetCurrentValidWindowSecs(1200 /* current time */));
     53   EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1300 /* current time */));
     54 }
     55 
     56 TEST(StrikeRegisterTest, NoStartupMode) {
     57   // Check that a strike register works immediately if NO_STARTUP_PERIOD_NEEDED
     58   // is specified.
     59   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     60                      100 /* window secs */, kOrbit,
     61                      StrikeRegister::NO_STARTUP_PERIOD_NEEDED);
     62   uint8 nonce[32];
     63   SetNonce(nonce, 1000, kOrbit);
     64   EXPECT_EQ(net::NONCE_OK, set.Insert(nonce, 1000));
     65   EXPECT_EQ(net::NONCE_NOT_UNIQUE_FAILURE, set.Insert(nonce, 1000));
     66 
     67   EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1000 /* current time */));
     68   EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1050 /* current time */));
     69   EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1100 /* current time */));
     70   EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1200 /* current time */));
     71   EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1300 /* current time */));
     72 }
     73 
     74 TEST(StrikeRegisterTest, WindowFuture) {
     75   // The set must reject values outside the window.
     76   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     77                      100 /* window secs */, kOrbit,
     78                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
     79   uint8 nonce[32];
     80   SetNonce(nonce, 1101, kOrbit);
     81   EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1000));
     82   SetNonce(nonce, 999, kOrbit);
     83   EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1100));
     84 }
     85 
     86 TEST(StrikeRegisterTest, BadOrbit) {
     87   // The set must reject values with the wrong orbit
     88   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     89                      100 /* window secs */, kOrbit,
     90                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
     91   uint8 nonce[32];
     92   static const uint8 kBadOrbit[8] = { 0, 0, 0, 0, 1, 1, 1, 1 };
     93   SetNonce(nonce, 1101, kBadOrbit);
     94   EXPECT_EQ(net::NONCE_INVALID_ORBIT_FAILURE, set.Insert(nonce, 1100));
     95 }
     96 
     97 TEST(StrikeRegisterTest, OneValue) {
     98   StrikeRegister set(10 /* max size */, 1000 /* current time */,
     99                      100 /* window secs */, kOrbit,
    100                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
    101   uint8 nonce[32];
    102   SetNonce(nonce, 1101, kOrbit);
    103   EXPECT_EQ(net::NONCE_OK, set.Insert(nonce, 1101));
    104 }
    105 
    106 TEST(StrikeRegisterTest, RejectDuplicate) {
    107   // The set must reject values with the wrong orbit
    108   StrikeRegister set(10 /* max size */, 1000 /* current time */,
    109                      100 /* window secs */, kOrbit,
    110                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
    111   uint8 nonce[32];
    112   SetNonce(nonce, 1101, kOrbit);
    113   EXPECT_EQ(net::NONCE_OK, set.Insert(nonce, 1101));
    114   EXPECT_EQ(net::NONCE_NOT_UNIQUE_FAILURE, set.Insert(nonce, 1101));
    115 }
    116 
    117 TEST(StrikeRegisterTest, HorizonUpdating) {
    118   StrikeRegister::StartupType startup_types[] = {
    119     StrikeRegister::DENY_REQUESTS_AT_STARTUP,
    120     StrikeRegister::NO_STARTUP_PERIOD_NEEDED
    121   };
    122 
    123   for (size_t type_idx = 0; type_idx < arraysize(startup_types); ++type_idx) {
    124     StrikeRegister set(5 /* max size */, 500 /* current time */,
    125                        100 /* window secs */, kOrbit,
    126                        startup_types[type_idx]);
    127     uint8 nonce[6][32];
    128     for (unsigned i = 0; i < 5; i++) {
    129       SetNonce(nonce[i], 1101 + i, kOrbit);
    130       nonce[i][31] = i;
    131       EXPECT_EQ(net::NONCE_OK, set.Insert(nonce[i], 1100));
    132     }
    133 
    134     // Valid window is still equal to |window_secs + 1|.
    135     EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1100));
    136 
    137     // This should push the oldest value out and force the horizon to
    138     // be updated.
    139     SetNonce(nonce[5], 1110, kOrbit);
    140     EXPECT_EQ(net::NONCE_OK, set.Insert(nonce[5], 1110));
    141     // Effective horizon is computed based on the timestamp of the
    142     // value that was pushed out.
    143     EXPECT_EQ(9u, set.GetCurrentValidWindowSecs(1110));
    144 
    145     SetNonce(nonce[5], 1111, kOrbit);
    146     EXPECT_EQ(net::NONCE_OK, set.Insert(nonce[5], 1110));
    147     EXPECT_EQ(8u, set.GetCurrentValidWindowSecs(1110));
    148 
    149     // This should be behind the horizon now:
    150     SetNonce(nonce[5], 1101, kOrbit);
    151     nonce[5][31] = 10;
    152     EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce[5], 1110));
    153 
    154     // Insert beyond the valid range.
    155     SetNonce(nonce[5], 1117, kOrbit);
    156     nonce[5][31] = 2;
    157     EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce[5], 1110));
    158 
    159     // Insert at the upper valid range.
    160     SetNonce(nonce[5], 1116, kOrbit);
    161     nonce[5][31] = 1;
    162     EXPECT_EQ(net::NONCE_OK, set.Insert(nonce[5], 1110));
    163 
    164     // This should be beyond the upper valid range now:
    165     SetNonce(nonce[5], 1116, kOrbit);
    166     nonce[5][31] = 2;
    167     EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce[5], 1110));
    168   }
    169 }
    170 
    171 TEST(StrikeRegisterTest, InsertMany) {
    172   StrikeRegister set(5000 /* max size */, 1000 /* current time */,
    173                      500 /* window secs */, kOrbit,
    174                      StrikeRegister::DENY_REQUESTS_AT_STARTUP);
    175 
    176   uint8 nonce[32];
    177   SetNonce(nonce, 1101, kOrbit);
    178   for (unsigned i = 0; i < 100000; i++) {
    179     SetNonce(nonce, 1101 + i/500, kOrbit);
    180     memcpy(nonce + 12, &i, sizeof(i));
    181     EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1100));
    182   }
    183 }
    184 
    185 
    186 // For the following test we create a slow, but simple, version of a
    187 // StrikeRegister. The behaviour of this object is much easier to understand
    188 // than the fully fledged version. We then create a test to show, empirically,
    189 // that the two objects have identical behaviour.
    190 
    191 // A SlowStrikeRegister has the same public interface as a StrikeRegister, but
    192 // is much slower. Hopefully it is also more obviously correct and we can
    193 // empirically test that their behaviours are identical.
    194 class SlowStrikeRegister {
    195  public:
    196   SlowStrikeRegister(unsigned max_entries, uint32 current_time,
    197                      uint32 window_secs, const uint8 orbit[8])
    198       : max_entries_(max_entries),
    199         window_secs_(window_secs),
    200         creation_time_(current_time),
    201         horizon_(ExternalTimeToInternal(current_time + window_secs) + 1) {
    202     memcpy(orbit_, orbit, sizeof(orbit_));
    203   }
    204 
    205   InsertStatus Insert(const uint8 nonce_bytes[32],
    206                       const uint32 nonce_time_external,
    207                       const uint32 current_time_external) {
    208     if (nonces_.size() == max_entries_) {
    209       DropOldestEntry();
    210     }
    211 
    212     const uint32 current_time = ExternalTimeToInternal(current_time_external);
    213 
    214     // Check to see if the orbit is correct.
    215     if (memcmp(nonce_bytes + 4, orbit_, sizeof(orbit_))) {
    216       return net::NONCE_INVALID_ORBIT_FAILURE;
    217     }
    218     const uint32 nonce_time =
    219         ExternalTimeToInternal(TimeFromBytes(nonce_bytes));
    220     EXPECT_EQ(ExternalTimeToInternal(nonce_time_external), nonce_time);
    221     // We have dropped one or more nonces with a time value of |horizon_ - 1|,
    222     // so we have to reject anything with a timestamp less than or
    223     // equal to that.
    224     if (nonce_time < horizon_) {
    225       return net::NONCE_INVALID_TIME_FAILURE;
    226     }
    227 
    228     // Check that the timestamp is in the current window.
    229     if ((current_time > window_secs_ &&
    230          nonce_time < (current_time - window_secs_)) ||
    231         nonce_time > (current_time + window_secs_)) {
    232       return net::NONCE_INVALID_TIME_FAILURE;
    233     }
    234 
    235     pair<uint32, string> nonce = make_pair(
    236         nonce_time,
    237         string(reinterpret_cast<const char*>(nonce_bytes), 32));
    238 
    239     set<pair<uint32, string> >::const_iterator it = nonces_.find(nonce);
    240     if (it != nonces_.end()) {
    241       return net::NONCE_NOT_UNIQUE_FAILURE;
    242     }
    243 
    244     nonces_.insert(nonce);
    245     return net::NONCE_OK;
    246   }
    247 
    248   uint32 GetCurrentValidWindowSecs(const uint32 current_time_external) const {
    249     const uint32 current_time = ExternalTimeToInternal(current_time_external);
    250     if (horizon_ > current_time) {
    251       return 0;
    252     }
    253     return 1 + min(current_time - horizon_, window_secs_);
    254   }
    255 
    256  private:
    257   // TimeFromBytes returns a big-endian uint32 from |d|.
    258   static uint32 TimeFromBytes(const uint8 d[4]) {
    259     return static_cast<uint32>(d[0]) << 24 |
    260            static_cast<uint32>(d[1]) << 16 |
    261            static_cast<uint32>(d[2]) << 8 |
    262            static_cast<uint32>(d[3]);
    263   }
    264 
    265   uint32 ExternalTimeToInternal(uint32 external_time) const {
    266     static const uint32 kCreationTimeFromInternalEpoch = 63115200.0;
    267     uint32 internal_epoch = 0;
    268     if (creation_time_ > kCreationTimeFromInternalEpoch) {
    269       internal_epoch = creation_time_ - kCreationTimeFromInternalEpoch;
    270     }
    271 
    272     return external_time - internal_epoch;
    273   }
    274 
    275   void DropOldestEntry() {
    276     set<pair<uint32, string> >::iterator oldest = nonces_.begin();
    277     horizon_ = oldest->first + 1;
    278     nonces_.erase(oldest);
    279   }
    280 
    281   const unsigned max_entries_;
    282   const unsigned window_secs_;
    283   const uint32 creation_time_;
    284   uint8 orbit_[8];
    285   uint32 horizon_;
    286 
    287   set<pair<uint32, string> > nonces_;
    288 };
    289 
    290 class StrikeRegisterStressTest : public ::testing::Test {
    291 };
    292 
    293 TEST_F(StrikeRegisterStressTest, InOrderInsertion) {
    294   // Fixed seed gives reproducibility for this test.
    295   srand(42);
    296 
    297   unsigned max_entries = 64;
    298   uint32 current_time = 10000, window = 200;
    299   scoped_ptr<StrikeRegister> s1(
    300       new StrikeRegister(max_entries, current_time, window, kOrbit,
    301                          StrikeRegister::DENY_REQUESTS_AT_STARTUP));
    302   scoped_ptr<SlowStrikeRegister> s2(
    303       new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
    304 
    305   uint64 i;
    306   const uint64 kMaxIterations = 10000;
    307   for (i = 0; i < kMaxIterations; i++) {
    308     const uint32 time = current_time + i;
    309 
    310     uint8 nonce[32];
    311     SetNonce(nonce, time, kOrbit);
    312 
    313     // There are 2048 possible nonce values:
    314     const uint32 v = rand() % 2048;
    315     nonce[30] = v >> 8;
    316     nonce[31] = v;
    317 
    318     const InsertStatus nonce_error2 = s2->Insert(nonce, time, time);
    319     const InsertStatus nonce_error1 = s1->Insert(nonce, time);
    320     EXPECT_EQ(nonce_error1, nonce_error2);
    321 
    322     // Inserts succeed after the startup period.
    323     if (time > current_time + window) {
    324       EXPECT_EQ(net::NONCE_OK, nonce_error1);
    325     } else {
    326       EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, nonce_error1);
    327     }
    328     EXPECT_EQ(s1->GetCurrentValidWindowSecs(time),
    329               s2->GetCurrentValidWindowSecs(time));
    330 
    331     if (i % 10 == 0) {
    332       s1->Validate();
    333     }
    334 
    335     if (HasFailure()) {
    336       break;
    337     }
    338   }
    339 
    340   if (i != kMaxIterations) {
    341     FAIL() << "Failed after " << i << " iterations";
    342   }
    343 }
    344 
    345 TEST_F(StrikeRegisterStressTest, Stress) {
    346   // Fixed seed gives reproducibility for this test.
    347   srand(42);
    348   unsigned max_entries = 64;
    349   uint32 current_time = 10000, window = 200;
    350   scoped_ptr<StrikeRegister> s1(
    351       new StrikeRegister(max_entries, current_time, window, kOrbit,
    352                          StrikeRegister::DENY_REQUESTS_AT_STARTUP));
    353   scoped_ptr<SlowStrikeRegister> s2(
    354       new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
    355   uint64 i;
    356 
    357   // When making changes it's worth removing the limit on this test and running
    358   // it for a while. For the initial development an opt binary was left running
    359   // for 10 minutes.
    360   const uint64 kMaxIterations = 10000;
    361   for (i = 0; i < kMaxIterations; i++) {
    362     if (rand() % 1000 == 0) {
    363       // 0.1% chance of resetting the sets.
    364       max_entries = rand() % 300 + 2;
    365       current_time = rand() % 10000;
    366       window = rand() % 500;
    367       s1.reset(new StrikeRegister(max_entries, current_time, window, kOrbit,
    368                                   StrikeRegister::DENY_REQUESTS_AT_STARTUP));
    369       s2.reset(
    370           new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
    371     }
    372 
    373     int32 time_delta = rand() % (window * 4);
    374     time_delta -= window * 2;
    375     const uint32 time = current_time + time_delta;
    376     if (time_delta < 0 && time > current_time) {
    377       continue;  // overflow
    378     }
    379 
    380     uint8 nonce[32];
    381     SetNonce(nonce, time, kOrbit);
    382 
    383     // There are 2048 possible nonce values:
    384     const uint32 v = rand() % 2048;
    385     nonce[30] = v >> 8;
    386     nonce[31] = v;
    387 
    388     const InsertStatus nonce_error2 = s2->Insert(nonce, time, time);
    389     const InsertStatus nonce_error1 = s1->Insert(nonce, time);
    390     EXPECT_EQ(nonce_error1, nonce_error2);
    391     EXPECT_EQ(s1->GetCurrentValidWindowSecs(time),
    392               s2->GetCurrentValidWindowSecs(time));
    393 
    394     if (i % 10 == 0) {
    395       s1->Validate();
    396     }
    397 
    398     if (HasFailure()) {
    399       break;
    400     }
    401   }
    402 
    403   if (i != kMaxIterations) {
    404     FAIL() << "Failed after " << i << " iterations";
    405   }
    406 }
    407 
    408 }  // anonymous namespace
    409