LCOV - code coverage report
Current view: top level - security/certverifier - OCSPCache.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 3 137 2.2 %
Date: 2017-07-14 16:53:18 Functions: 1 11 9.1 %
Legend: Lines: hit not hit

          Line data    Source code
       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 code is made available to you under your choice of the following sets
       4             :  * of licensing terms:
       5             :  */
       6             : /* This Source Code Form is subject to the terms of the Mozilla Public
       7             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       8             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
       9             :  */
      10             : /* Copyright 2013 Mozilla Contributors
      11             :  *
      12             :  * Licensed under the Apache License, Version 2.0 (the "License");
      13             :  * you may not use this file except in compliance with the License.
      14             :  * You may obtain a copy of the License at
      15             :  *
      16             :  *     http://www.apache.org/licenses/LICENSE-2.0
      17             :  *
      18             :  * Unless required by applicable law or agreed to in writing, software
      19             :  * distributed under the License is distributed on an "AS IS" BASIS,
      20             :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      21             :  * See the License for the specific language governing permissions and
      22             :  * limitations under the License.
      23             :  */
      24             : 
      25             : #include "OCSPCache.h"
      26             : 
      27             : #include <limits>
      28             : 
      29             : #include "NSSCertDBTrustDomain.h"
      30             : #include "pk11pub.h"
      31             : #include "pkix/pkixnss.h"
      32             : #include "ScopedNSSTypes.h"
      33             : #include "secerr.h"
      34             : 
      35             : extern mozilla::LazyLogModule gCertVerifierLog;
      36             : 
      37             : using namespace mozilla::pkix;
      38             : 
      39             : namespace mozilla { namespace psm {
      40             : 
      41             : typedef mozilla::pkix::Result Result;
      42             : 
      43             : static SECStatus
      44           0 : DigestLength(UniquePK11Context& context, uint32_t length)
      45             : {
      46             :   // Restrict length to 2 bytes because it should be big enough for all
      47             :   // inputs this code will actually see and that it is well-defined and
      48             :   // type-size-independent.
      49           0 :   if (length >= 65536) {
      50           0 :     return SECFailure;
      51             :   }
      52             :   unsigned char array[2];
      53           0 :   array[0] = length & 255;
      54           0 :   array[1] = (length >> 8) & 255;
      55             : 
      56           0 :   return PK11_DigestOp(context.get(), array, MOZ_ARRAY_LENGTH(array));
      57             : }
      58             : 
      59             : // Let derIssuer be the DER encoding of the issuer of certID.
      60             : // Let derPublicKey be the DER encoding of the public key of certID.
      61             : // Let serialNumber be the bytes of the serial number of certID.
      62             : // Let serialNumberLen be the number of bytes of serialNumber.
      63             : // Let firstPartyDomain be the first party domain of originAttributes.
      64             : // It is only non-empty when "privacy.firstParty.isolate" is enabled, in order
      65             : // to isolate OCSP cache by first party.
      66             : // Let firstPartyDomainLen be the number of bytes of firstPartyDomain.
      67             : // The value calculated is SHA384(derIssuer || derPublicKey || serialNumberLen
      68             : // || serialNumber || firstPartyDomainLen || firstPartyDomain).
      69             : // Because the DER encodings include the length of the data encoded, and we also
      70             : // include the length of serialNumber and originAttributes, there do not exist
      71             : // A(derIssuerA, derPublicKeyA, serialNumberLenA, serialNumberA,
      72             : // originAttributesLenA, originAttributesA) and B(derIssuerB, derPublicKeyB,
      73             : // serialNumberLenB, serialNumberB, originAttributesLenB, originAttributesB)
      74             : // such that the concatenation of each tuple results in the same string of
      75             : // bytes but where each part in A is not equal to its counterpart in B. This is
      76             : // important because as a result it is computationally infeasible to find
      77             : // collisions that would subvert this cache (given that SHA384 is a
      78             : // cryptographically-secure hash function).
      79             : static SECStatus
      80           0 : CertIDHash(SHA384Buffer& buf, const CertID& certID,
      81             :            const OriginAttributes& originAttributes)
      82             : {
      83           0 :   UniquePK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
      84           0 :   if (!context) {
      85           0 :     return SECFailure;
      86             :   }
      87           0 :   SECStatus rv = PK11_DigestBegin(context.get());
      88           0 :   if (rv != SECSuccess) {
      89           0 :     return rv;
      90             :   }
      91           0 :   SECItem certIDIssuer = UnsafeMapInputToSECItem(certID.issuer);
      92           0 :   rv = PK11_DigestOp(context.get(), certIDIssuer.data, certIDIssuer.len);
      93           0 :   if (rv != SECSuccess) {
      94           0 :     return rv;
      95             :   }
      96             :   SECItem certIDIssuerSubjectPublicKeyInfo =
      97           0 :     UnsafeMapInputToSECItem(certID.issuerSubjectPublicKeyInfo);
      98           0 :   rv = PK11_DigestOp(context.get(), certIDIssuerSubjectPublicKeyInfo.data,
      99           0 :                      certIDIssuerSubjectPublicKeyInfo.len);
     100           0 :   if (rv != SECSuccess) {
     101           0 :     return rv;
     102             :   }
     103             :   SECItem certIDSerialNumber =
     104           0 :     UnsafeMapInputToSECItem(certID.serialNumber);
     105           0 :   rv = DigestLength(context, certIDSerialNumber.len);
     106           0 :   if (rv != SECSuccess) {
     107           0 :     return rv;
     108             :   }
     109           0 :   rv = PK11_DigestOp(context.get(), certIDSerialNumber.data,
     110           0 :                      certIDSerialNumber.len);
     111           0 :   if (rv != SECSuccess) {
     112           0 :     return rv;
     113             :   }
     114             : 
     115             :   // OCSP should not be isolated by containers.
     116           0 :   NS_ConvertUTF16toUTF8 firstPartyDomain(originAttributes.mFirstPartyDomain);
     117           0 :   if (!firstPartyDomain.IsEmpty()) {
     118           0 :     rv = DigestLength(context, firstPartyDomain.Length());
     119           0 :     if (rv != SECSuccess) {
     120           0 :       return rv;
     121             :     }
     122           0 :     rv = PK11_DigestOp(context.get(),
     123             :                        BitwiseCast<const unsigned char*>(firstPartyDomain.get()),
     124           0 :                        firstPartyDomain.Length());
     125           0 :     if (rv != SECSuccess) {
     126           0 :       return rv;
     127             :     }
     128             :   }
     129           0 :   uint32_t outLen = 0;
     130           0 :   rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
     131           0 :   if (outLen != SHA384_LENGTH) {
     132           0 :     return SECFailure;
     133             :   }
     134           0 :   return rv;
     135             : }
     136             : 
     137             : Result
     138           0 : OCSPCache::Entry::Init(const CertID& aCertID,
     139             :                        const OriginAttributes& aOriginAttributes)
     140             : {
     141           0 :   SECStatus srv = CertIDHash(mIDHash, aCertID, aOriginAttributes);
     142           0 :   if (srv != SECSuccess) {
     143           0 :     return MapPRErrorCodeToResult(PR_GetError());
     144             :   }
     145           0 :   return Success;
     146             : }
     147             : 
     148           1 : OCSPCache::OCSPCache()
     149           1 :   : mMutex("OCSPCache-mutex")
     150             : {
     151           1 : }
     152             : 
     153           0 : OCSPCache::~OCSPCache()
     154             : {
     155           0 :   Clear();
     156           0 : }
     157             : 
     158             : // Returns false with index in an undefined state if no matching entry was
     159             : // found.
     160             : bool
     161           0 : OCSPCache::FindInternal(const CertID& aCertID,
     162             :                         const OriginAttributes& aOriginAttributes,
     163             :                         /*out*/ size_t& index,
     164             :                         const MutexAutoLock& /* aProofOfLock */)
     165             : {
     166           0 :   if (mEntries.length() == 0) {
     167           0 :     return false;
     168             :   }
     169             : 
     170             :   SHA384Buffer idHash;
     171           0 :   SECStatus rv = CertIDHash(idHash, aCertID, aOriginAttributes);
     172           0 :   if (rv != SECSuccess) {
     173           0 :     return false;
     174             :   }
     175             : 
     176             :   // mEntries is sorted with the most-recently-used entry at the end.
     177             :   // Thus, searching from the end will often be fastest.
     178           0 :   index = mEntries.length();
     179           0 :   while (index > 0) {
     180           0 :     --index;
     181           0 :     if (memcmp(mEntries[index]->mIDHash, idHash, SHA384_LENGTH) == 0) {
     182           0 :       return true;
     183             :     }
     184             :   }
     185           0 :   return false;
     186             : }
     187             : 
     188             : static inline void
     189           0 : LogWithCertID(const char* aMessage, const CertID& aCertID,
     190             :               const OriginAttributes& aOriginAttributes)
     191             : {
     192           0 :   NS_ConvertUTF16toUTF8 firstPartyDomain(aOriginAttributes.mFirstPartyDomain);
     193           0 :   MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
     194             :           (aMessage, &aCertID, firstPartyDomain.get()));
     195           0 : }
     196             : 
     197             : void
     198           0 : OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
     199             :                                 const MutexAutoLock& /* aProofOfLock */)
     200             : {
     201           0 :   Entry* entry = mEntries[aIndex];
     202             :   // Since mEntries is sorted with the most-recently-used entry at the end,
     203             :   // aIndex is likely to be near the end, so this is likely to be fast.
     204           0 :   mEntries.erase(mEntries.begin() + aIndex);
     205             :   // erase() does not shrink or realloc memory, so the append below should
     206             :   // always succeed.
     207           0 :   MOZ_RELEASE_ASSERT(mEntries.append(entry));
     208           0 : }
     209             : 
     210             : bool
     211           0 : OCSPCache::Get(const CertID& aCertID,
     212             :                const OriginAttributes& aOriginAttributes,
     213             :                Result& aResult, Time& aValidThrough)
     214             : {
     215           0 :   MutexAutoLock lock(mMutex);
     216             : 
     217             :   size_t index;
     218           0 :   if (!FindInternal(aCertID, aOriginAttributes, index, lock)) {
     219             :     LogWithCertID("OCSPCache::Get(%p,\"%s\") not in cache", aCertID,
     220           0 :                   aOriginAttributes);
     221           0 :     return false;
     222             :   }
     223             :   LogWithCertID("OCSPCache::Get(%p,\"%s\") in cache", aCertID,
     224           0 :                 aOriginAttributes);
     225           0 :   aResult = mEntries[index]->mResult;
     226           0 :   aValidThrough = mEntries[index]->mValidThrough;
     227           0 :   MakeMostRecentlyUsed(index, lock);
     228           0 :   return true;
     229             : }
     230             : 
     231             : Result
     232           0 : OCSPCache::Put(const CertID& aCertID,
     233             :                const OriginAttributes& aOriginAttributes,
     234             :                Result aResult, Time aThisUpdate, Time aValidThrough)
     235             : {
     236           0 :   MutexAutoLock lock(mMutex);
     237             : 
     238             :   size_t index;
     239           0 :   if (FindInternal(aCertID, aOriginAttributes, index, lock)) {
     240             :     // Never replace an entry indicating a revoked certificate.
     241           0 :     if (mEntries[index]->mResult == Result::ERROR_REVOKED_CERTIFICATE) {
     242             :       LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache as revoked - "
     243           0 :                     "not replacing", aCertID, aOriginAttributes);
     244           0 :       MakeMostRecentlyUsed(index, lock);
     245           0 :       return Success;
     246             :     }
     247             : 
     248             :     // Never replace a newer entry with an older one unless the older entry
     249             :     // indicates a revoked certificate, which we want to remember.
     250           0 :     if (mEntries[index]->mThisUpdate > aThisUpdate &&
     251             :         aResult != Result::ERROR_REVOKED_CERTIFICATE) {
     252             :       LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache with more "
     253             :                     "recent validity - not replacing", aCertID,
     254           0 :                     aOriginAttributes);
     255           0 :       MakeMostRecentlyUsed(index, lock);
     256           0 :       return Success;
     257             :     }
     258             : 
     259             :     // Only known good responses or responses indicating an unknown
     260             :     // or revoked certificate should replace previously known responses.
     261           0 :     if (aResult != Success &&
     262           0 :         aResult != Result::ERROR_OCSP_UNKNOWN_CERT &&
     263             :         aResult != Result::ERROR_REVOKED_CERTIFICATE) {
     264             :       LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - not "
     265             :                     "replacing with less important status", aCertID,
     266           0 :                     aOriginAttributes);
     267           0 :       MakeMostRecentlyUsed(index, lock);
     268           0 :       return Success;
     269             :     }
     270             : 
     271             :     LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - replacing",
     272           0 :                   aCertID, aOriginAttributes);
     273           0 :     mEntries[index]->mResult = aResult;
     274           0 :     mEntries[index]->mThisUpdate = aThisUpdate;
     275           0 :     mEntries[index]->mValidThrough = aValidThrough;
     276           0 :     MakeMostRecentlyUsed(index, lock);
     277           0 :     return Success;
     278             :   }
     279             : 
     280           0 :   if (mEntries.length() == MaxEntries) {
     281             :     LogWithCertID("OCSPCache::Put(%p, \"%s\") too full - evicting an entry",
     282           0 :                   aCertID, aOriginAttributes);
     283           0 :     for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
     284             :          toEvict++) {
     285             :       // Never evict an entry that indicates a revoked or unknokwn certificate,
     286             :       // because revoked responses are more security-critical to remember.
     287           0 :       if ((*toEvict)->mResult != Result::ERROR_REVOKED_CERTIFICATE &&
     288           0 :           (*toEvict)->mResult != Result::ERROR_OCSP_UNKNOWN_CERT) {
     289           0 :         delete *toEvict;
     290           0 :         mEntries.erase(toEvict);
     291           0 :         break;
     292             :       }
     293             :     }
     294             :     // Well, we tried, but apparently everything is revoked or unknown.
     295             :     // We don't want to remove a cached revoked or unknown response. If we're
     296             :     // trying to insert a good response, we can just return "successfully"
     297             :     // without doing so. This means we'll lose some speed, but it's not a
     298             :     // security issue. If we're trying to insert a revoked or unknown response,
     299             :     // we can't. We should return with an error that causes the current
     300             :     // verification to fail.
     301           0 :     if (mEntries.length() == MaxEntries) {
     302           0 :       return aResult;
     303             :     }
     304             :   }
     305             : 
     306             :   Entry* newEntry = new (std::nothrow) Entry(aResult, aThisUpdate,
     307           0 :                                              aValidThrough);
     308             :   // Normally we don't have to do this in Gecko, because OOM is fatal.
     309             :   // However, if we want to embed this in another project, OOM might not
     310             :   // be fatal, so handle this case.
     311           0 :   if (!newEntry) {
     312           0 :     return Result::FATAL_ERROR_NO_MEMORY;
     313             :   }
     314           0 :   Result rv = newEntry->Init(aCertID, aOriginAttributes);
     315           0 :   if (rv != Success) {
     316           0 :     delete newEntry;
     317           0 :     return rv;
     318             :   }
     319           0 :   if (!mEntries.append(newEntry)) {
     320           0 :     delete newEntry;
     321           0 :     return Result::FATAL_ERROR_NO_MEMORY;
     322             :   }
     323             :   LogWithCertID("OCSPCache::Put(%p, \"%s\") added to cache", aCertID,
     324           0 :                 aOriginAttributes);
     325           0 :   return Success;
     326             : }
     327             : 
     328             : void
     329           0 : OCSPCache::Clear()
     330             : {
     331           0 :   MutexAutoLock lock(mMutex);
     332           0 :   MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("OCSPCache::Clear: clearing cache"));
     333             :   // First go through and delete the memory being pointed to by the pointers
     334             :   // in the vector.
     335           0 :   for (Entry** entry = mEntries.begin(); entry < mEntries.end();
     336             :        entry++) {
     337           0 :     delete *entry;
     338             :   }
     339             :   // Then remove the pointers themselves.
     340           0 :   mEntries.clearAndFree();
     341           0 : }
     342             : 
     343             : } } // namespace mozilla::psm

Generated by: LCOV version 1.13