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 }