tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

OCSPCacheTest.cpp (14280B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "CertVerifier.h"
      8 #include "OCSPCache.h"
      9 #include "gtest/gtest.h"
     10 #include "mozilla/BasePrincipal.h"
     11 #include "mozilla/Casting.h"
     12 #include "mozilla/Preferences.h"
     13 #include "mozilla/Sprintf.h"
     14 #include "nss.h"
     15 #include "mozpkix/pkixtypes.h"
     16 #include "mozpkix/test/pkixtestutil.h"
     17 #include "prerr.h"
     18 #include "secerr.h"
     19 
     20 using namespace mozilla::pkix;
     21 using namespace mozilla::pkix::test;
     22 
     23 using mozilla::OriginAttributes;
     24 
     25 template <size_t N>
     26 inline Input LiteralInput(const char (&valueString)[N]) {
     27  // Ideally we would use mozilla::BitwiseCast() here rather than
     28  // reinterpret_cast for better type checking, but the |N - 1| part trips
     29  // static asserts.
     30  return Input(reinterpret_cast<const uint8_t(&)[N - 1]>(valueString));
     31 }
     32 
     33 const int MaxCacheEntries = 1024;
     34 
     35 class psm_OCSPCacheTest : public ::testing::Test {
     36 protected:
     37  psm_OCSPCacheTest() : now(Now()) {}
     38 
     39  static void SetUpTestCase() { NSS_NoDB_Init(nullptr); }
     40 
     41  const Time now;
     42  mozilla::psm::OCSPCache cache;
     43 };
     44 
     45 static void PutAndGet(
     46    mozilla::psm::OCSPCache& cache, const CertID& certID, Result result,
     47    Time time, const OriginAttributes& originAttributes = OriginAttributes()) {
     48  // The first time is thisUpdate. The second is validUntil.
     49  // The caller is expecting the validUntil returned with Get
     50  // to be equal to the passed-in time. Since these values will
     51  // be different in practice, make thisUpdate less than validUntil.
     52  Time thisUpdate(time);
     53  ASSERT_EQ(Success, thisUpdate.SubtractSeconds(10));
     54  Result rv = cache.Put(certID, originAttributes, result, thisUpdate, time);
     55  ASSERT_TRUE(rv == Success);
     56  Result resultOut;
     57  Time timeOut(Time::uninitialized);
     58  ASSERT_TRUE(cache.Get(certID, originAttributes, resultOut, timeOut));
     59  ASSERT_EQ(result, resultOut);
     60  ASSERT_EQ(time, timeOut);
     61 }
     62 
     63 MOZ_RUNINIT Input fakeIssuer1(LiteralInput("CN=issuer1"));
     64 MOZ_RUNINIT Input fakeKey000(LiteralInput("key000"));
     65 MOZ_RUNINIT Input fakeKey001(LiteralInput("key001"));
     66 MOZ_RUNINIT Input fakeSerial0000(LiteralInput("0000"));
     67 
     68 TEST_F(psm_OCSPCacheTest, TestPutAndGet) {
     69  Input fakeSerial000(LiteralInput("000"));
     70  Input fakeSerial001(LiteralInput("001"));
     71 
     72  SCOPED_TRACE("");
     73  PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial001), Success,
     74            now);
     75  Result resultOut;
     76  Time timeOut(Time::uninitialized);
     77  ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial000),
     78                         OriginAttributes(), resultOut, timeOut));
     79 }
     80 
     81 TEST_F(psm_OCSPCacheTest, TestVariousGets) {
     82  SCOPED_TRACE("");
     83  for (int i = 0; i < MaxCacheEntries; i++) {
     84    uint8_t serialBuf[8];
     85    snprintf(mozilla::BitwiseCast<char*, uint8_t*>(serialBuf),
     86             sizeof(serialBuf), "%04d", i);
     87    Input fakeSerial;
     88    ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4));
     89    Time timeIn(now);
     90    ASSERT_EQ(Success, timeIn.AddSeconds(i));
     91    PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial), Success,
     92              timeIn);
     93  }
     94 
     95  Time timeIn(now);
     96  Result resultOut;
     97  Time timeOut(Time::uninitialized);
     98 
     99  // This will be at the end of the list in the cache
    100  CertID cert0000(fakeIssuer1, fakeKey000, fakeSerial0000);
    101  ASSERT_TRUE(cache.Get(cert0000, OriginAttributes(), resultOut, timeOut));
    102  ASSERT_EQ(Success, resultOut);
    103  ASSERT_EQ(timeIn, timeOut);
    104  // Once we access it, it goes to the front
    105  ASSERT_TRUE(cache.Get(cert0000, OriginAttributes(), resultOut, timeOut));
    106  ASSERT_EQ(Success, resultOut);
    107  ASSERT_EQ(timeIn, timeOut);
    108 
    109  // This will be in the middle
    110  Time timeInPlus512(now);
    111  ASSERT_EQ(Success, timeInPlus512.AddSeconds(512));
    112 
    113  static const Input fakeSerial0512(LiteralInput("0512"));
    114  CertID cert0512(fakeIssuer1, fakeKey000, fakeSerial0512);
    115  ASSERT_TRUE(cache.Get(cert0512, OriginAttributes(), resultOut, timeOut));
    116  ASSERT_EQ(Success, resultOut);
    117  ASSERT_EQ(timeInPlus512, timeOut);
    118  ASSERT_TRUE(cache.Get(cert0512, OriginAttributes(), resultOut, timeOut));
    119  ASSERT_EQ(Success, resultOut);
    120  ASSERT_EQ(timeInPlus512, timeOut);
    121 
    122  // We've never seen this certificate
    123  static const Input fakeSerial1111(LiteralInput("1111"));
    124  ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey000, fakeSerial1111),
    125                         OriginAttributes(), resultOut, timeOut));
    126 }
    127 
    128 TEST_F(psm_OCSPCacheTest, TestEviction) {
    129  SCOPED_TRACE("");
    130  // By putting more distinct entries in the cache than it can hold,
    131  // we cause the least recently used entry to be evicted.
    132  for (int i = 0; i < MaxCacheEntries + 1; i++) {
    133    uint8_t serialBuf[8];
    134    snprintf(mozilla::BitwiseCast<char*, uint8_t*>(serialBuf),
    135             sizeof(serialBuf), "%04d", i);
    136    Input fakeSerial;
    137    ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4));
    138    Time timeIn(now);
    139    ASSERT_EQ(Success, timeIn.AddSeconds(i));
    140    PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial), Success,
    141              timeIn);
    142  }
    143 
    144  Result resultOut;
    145  Time timeOut(Time::uninitialized);
    146  ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial0000),
    147                         OriginAttributes(), resultOut, timeOut));
    148 }
    149 
    150 TEST_F(psm_OCSPCacheTest, TestNoEvictionForRevokedResponses) {
    151  SCOPED_TRACE("");
    152  CertID notEvicted(fakeIssuer1, fakeKey000, fakeSerial0000);
    153  Time timeIn(now);
    154  PutAndGet(cache, notEvicted, Result::ERROR_REVOKED_CERTIFICATE, timeIn);
    155  // By putting more distinct entries in the cache than it can hold,
    156  // we cause the least recently used entry that isn't revoked to be evicted.
    157  for (int i = 1; i < MaxCacheEntries + 1; i++) {
    158    uint8_t serialBuf[8];
    159    snprintf(mozilla::BitwiseCast<char*, uint8_t*>(serialBuf),
    160             sizeof(serialBuf), "%04d", i);
    161    Input fakeSerial;
    162    ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4));
    163    Time timeIn(now);
    164    ASSERT_EQ(Success, timeIn.AddSeconds(i));
    165    PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial), Success,
    166              timeIn);
    167  }
    168  Result resultOut;
    169  Time timeOut(Time::uninitialized);
    170  ASSERT_TRUE(cache.Get(notEvicted, OriginAttributes(), resultOut, timeOut));
    171  ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, resultOut);
    172  ASSERT_EQ(timeIn, timeOut);
    173 
    174  Input fakeSerial0001(LiteralInput("0001"));
    175  CertID evicted(fakeIssuer1, fakeKey000, fakeSerial0001);
    176  ASSERT_FALSE(cache.Get(evicted, OriginAttributes(), resultOut, timeOut));
    177 }
    178 
    179 TEST_F(psm_OCSPCacheTest, TestEverythingIsRevoked) {
    180  SCOPED_TRACE("");
    181  Time timeIn(now);
    182  // Fill up the cache with revoked responses.
    183  for (int i = 0; i < MaxCacheEntries; i++) {
    184    uint8_t serialBuf[8];
    185    snprintf(mozilla::BitwiseCast<char*, uint8_t*>(serialBuf),
    186             sizeof(serialBuf), "%04d", i);
    187    Input fakeSerial;
    188    ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4));
    189    Time timeIn(now);
    190    ASSERT_EQ(Success, timeIn.AddSeconds(i));
    191    PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial),
    192              Result::ERROR_REVOKED_CERTIFICATE, timeIn);
    193  }
    194  static const Input fakeSerial1025(LiteralInput("1025"));
    195  CertID good(fakeIssuer1, fakeKey000, fakeSerial1025);
    196  // This will "succeed", allowing verification to continue. However,
    197  // nothing was actually put in the cache.
    198  Time timeInPlus1025(timeIn);
    199  ASSERT_EQ(Success, timeInPlus1025.AddSeconds(1025));
    200  Time timeInPlus1025Minus50(timeInPlus1025);
    201  ASSERT_EQ(Success, timeInPlus1025Minus50.SubtractSeconds(50));
    202  Result result = cache.Put(good, OriginAttributes(), Success,
    203                            timeInPlus1025Minus50, timeInPlus1025);
    204  ASSERT_EQ(Success, result);
    205  Result resultOut;
    206  Time timeOut(Time::uninitialized);
    207  ASSERT_FALSE(cache.Get(good, OriginAttributes(), resultOut, timeOut));
    208 
    209  static const Input fakeSerial1026(LiteralInput("1026"));
    210  CertID revoked(fakeIssuer1, fakeKey000, fakeSerial1026);
    211  // This will fail, causing verification to fail.
    212  Time timeInPlus1026(timeIn);
    213  ASSERT_EQ(Success, timeInPlus1026.AddSeconds(1026));
    214  Time timeInPlus1026Minus50(timeInPlus1026);
    215  ASSERT_EQ(Success, timeInPlus1026Minus50.SubtractSeconds(50));
    216  result =
    217      cache.Put(revoked, OriginAttributes(), Result::ERROR_REVOKED_CERTIFICATE,
    218                timeInPlus1026Minus50, timeInPlus1026);
    219  ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, result);
    220 }
    221 
    222 TEST_F(psm_OCSPCacheTest, VariousIssuers) {
    223  SCOPED_TRACE("");
    224  Time timeIn(now);
    225  static const Input fakeIssuer2(LiteralInput("CN=issuer2"));
    226  static const Input fakeSerial001(LiteralInput("001"));
    227  CertID subject(fakeIssuer1, fakeKey000, fakeSerial001);
    228  PutAndGet(cache, subject, Success, now);
    229  Result resultOut;
    230  Time timeOut(Time::uninitialized);
    231  ASSERT_TRUE(cache.Get(subject, OriginAttributes(), resultOut, timeOut));
    232  ASSERT_EQ(Success, resultOut);
    233  ASSERT_EQ(timeIn, timeOut);
    234  // Test that we don't match a different issuer DN
    235  ASSERT_FALSE(cache.Get(CertID(fakeIssuer2, fakeKey000, fakeSerial001),
    236                         OriginAttributes(), resultOut, timeOut));
    237  // Test that we don't match a different issuer key
    238  ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial001),
    239                         OriginAttributes(), resultOut, timeOut));
    240 }
    241 
    242 TEST_F(psm_OCSPCacheTest, Times) {
    243  SCOPED_TRACE("");
    244  CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000);
    245  PutAndGet(cache, certID, Result::ERROR_OCSP_UNKNOWN_CERT,
    246            TimeFromElapsedSecondsAD(100));
    247  PutAndGet(cache, certID, Success, TimeFromElapsedSecondsAD(200));
    248  // This should not override the more recent entry.
    249  ASSERT_EQ(
    250      Success,
    251      cache.Put(certID, OriginAttributes(), Result::ERROR_OCSP_UNKNOWN_CERT,
    252                TimeFromElapsedSecondsAD(100), TimeFromElapsedSecondsAD(100)));
    253  Result resultOut;
    254  Time timeOut(Time::uninitialized);
    255  ASSERT_TRUE(cache.Get(certID, OriginAttributes(), resultOut, timeOut));
    256  // Here we see the more recent time.
    257  ASSERT_EQ(Success, resultOut);
    258  ASSERT_EQ(TimeFromElapsedSecondsAD(200), timeOut);
    259 
    260  // Result::ERROR_REVOKED_CERTIFICATE overrides everything
    261  PutAndGet(cache, certID, Result::ERROR_REVOKED_CERTIFICATE,
    262            TimeFromElapsedSecondsAD(50));
    263 }
    264 
    265 TEST_F(psm_OCSPCacheTest, NetworkFailure) {
    266  SCOPED_TRACE("");
    267  CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000);
    268  PutAndGet(cache, certID, Result::ERROR_CONNECT_REFUSED,
    269            TimeFromElapsedSecondsAD(100));
    270  PutAndGet(cache, certID, Success, TimeFromElapsedSecondsAD(200));
    271  // This should not override the already present entry.
    272  ASSERT_EQ(
    273      Success,
    274      cache.Put(certID, OriginAttributes(), Result::ERROR_CONNECT_REFUSED,
    275                TimeFromElapsedSecondsAD(300), TimeFromElapsedSecondsAD(350)));
    276  Result resultOut;
    277  Time timeOut(Time::uninitialized);
    278  ASSERT_TRUE(cache.Get(certID, OriginAttributes(), resultOut, timeOut));
    279  ASSERT_EQ(Success, resultOut);
    280  ASSERT_EQ(TimeFromElapsedSecondsAD(200), timeOut);
    281 
    282  PutAndGet(cache, certID, Result::ERROR_OCSP_UNKNOWN_CERT,
    283            TimeFromElapsedSecondsAD(400));
    284  // This should not override the already present entry.
    285  ASSERT_EQ(
    286      Success,
    287      cache.Put(certID, OriginAttributes(), Result::ERROR_CONNECT_REFUSED,
    288                TimeFromElapsedSecondsAD(500), TimeFromElapsedSecondsAD(550)));
    289  ASSERT_TRUE(cache.Get(certID, OriginAttributes(), resultOut, timeOut));
    290  ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT, resultOut);
    291  ASSERT_EQ(TimeFromElapsedSecondsAD(400), timeOut);
    292 
    293  PutAndGet(cache, certID, Result::ERROR_REVOKED_CERTIFICATE,
    294            TimeFromElapsedSecondsAD(600));
    295  // This should not override the already present entry.
    296  ASSERT_EQ(
    297      Success,
    298      cache.Put(certID, OriginAttributes(), Result::ERROR_CONNECT_REFUSED,
    299                TimeFromElapsedSecondsAD(700), TimeFromElapsedSecondsAD(750)));
    300  ASSERT_TRUE(cache.Get(certID, OriginAttributes(), resultOut, timeOut));
    301  ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, resultOut);
    302  ASSERT_EQ(TimeFromElapsedSecondsAD(600), timeOut);
    303 }
    304 
    305 TEST_F(psm_OCSPCacheTest, TestOriginAttributes) {
    306  CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000);
    307 
    308  // We test two attributes, firstPartyDomain and partitionKey, respectively
    309  // because we don't have entries that have both attributes set because the two
    310  // features that use these attributes are mutually exclusive.
    311 
    312  // Set pref for OCSP cache network partitioning.
    313  mozilla::Preferences::SetBool("privacy.partition.network_state.ocsp_cache",
    314                                true);
    315 
    316  SCOPED_TRACE("");
    317  OriginAttributes attrs;
    318  attrs.mFirstPartyDomain.AssignLiteral("foo.com");
    319  PutAndGet(cache, certID, Success, now, attrs);
    320 
    321  Result resultOut;
    322  Time timeOut(Time::uninitialized);
    323  attrs.mFirstPartyDomain.AssignLiteral("bar.com");
    324  ASSERT_FALSE(cache.Get(certID, attrs, resultOut, timeOut));
    325 
    326  // OCSP cache should not be isolated by containers for firstPartyDomain.
    327  attrs.mUserContextId = 1;
    328  attrs.mFirstPartyDomain.AssignLiteral("foo.com");
    329  ASSERT_TRUE(cache.Get(certID, attrs, resultOut, timeOut));
    330 
    331  // Clear originAttributes.
    332  attrs.mUserContextId = 0;
    333  attrs.mFirstPartyDomain.Truncate();
    334 
    335  // Add OCSP cache for the partitionKey.
    336  attrs.mPartitionKey.AssignLiteral("(https,foo.com)");
    337  PutAndGet(cache, certID, Success, now, attrs);
    338 
    339  // Check cache entry for the partitionKey.
    340  attrs.mPartitionKey.AssignLiteral("(https,foo.com)");
    341  ASSERT_TRUE(cache.Get(certID, attrs, resultOut, timeOut));
    342 
    343  // OCSP cache entry should not exist for the other partitionKey.
    344  attrs.mPartitionKey.AssignLiteral("(https,bar.com)");
    345  ASSERT_FALSE(cache.Get(certID, attrs, resultOut, timeOut));
    346 
    347  // OCSP cache should not be isolated by containers for partitonKey.
    348  attrs.mUserContextId = 1;
    349  attrs.mPartitionKey.AssignLiteral("(https,foo.com)");
    350  ASSERT_TRUE(cache.Get(certID, attrs, resultOut, timeOut));
    351 
    352  // OCSP cache should not exist for the OAs which has both attributes set.
    353  attrs.mUserContextId = 0;
    354  attrs.mFirstPartyDomain.AssignLiteral("foo.com");
    355  attrs.mPartitionKey.AssignLiteral("(https,foo.com)");
    356  ASSERT_FALSE(cache.Get(certID, attrs, resultOut, timeOut));
    357 }