security/certverifier/OCSPCache.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/security/certverifier/OCSPCache.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,299 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */
     1.6 +/* Copyright 2013 Mozilla Foundation
     1.7 + *
     1.8 + * Licensed under the Apache License, Version 2.0 (the "License");
     1.9 + * you may not use this file except in compliance with the License.
    1.10 + * You may obtain a copy of the License at
    1.11 + *
    1.12 + *     http://www.apache.org/licenses/LICENSE-2.0
    1.13 + *
    1.14 + * Unless required by applicable law or agreed to in writing, software
    1.15 + * distributed under the License is distributed on an "AS IS" BASIS,
    1.16 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    1.17 + * See the License for the specific language governing permissions and
    1.18 + * limitations under the License.
    1.19 + */
    1.20 +
    1.21 +#include "OCSPCache.h"
    1.22 +
    1.23 +#include "NSSCertDBTrustDomain.h"
    1.24 +#include "pk11pub.h"
    1.25 +#include "secerr.h"
    1.26 +
    1.27 +#ifdef PR_LOGGING
    1.28 +extern PRLogModuleInfo* gCertVerifierLog;
    1.29 +#endif
    1.30 +
    1.31 +namespace mozilla { namespace psm {
    1.32 +
    1.33 +void
    1.34 +MozillaPKIX_PK11_DestroyContext_true(PK11Context* context)
    1.35 +{
    1.36 +  PK11_DestroyContext(context, true);
    1.37 +}
    1.38 +
    1.39 +typedef mozilla::pkix::ScopedPtr<PK11Context,
    1.40 +                                 MozillaPKIX_PK11_DestroyContext_true>
    1.41 +                                 ScopedPK11Context;
    1.42 +
    1.43 +// Let derIssuer be the DER encoding of the issuer of aCert.
    1.44 +// Let derPublicKey be the DER encoding of the public key of aIssuerCert.
    1.45 +// Let serialNumber be the bytes of the serial number of aCert.
    1.46 +// The value calculated is SHA384(derIssuer || derPublicKey || serialNumber).
    1.47 +// Because the DER encodings include the length of the data encoded,
    1.48 +// there do not exist A(derIssuerA, derPublicKeyA, serialNumberA) and
    1.49 +// B(derIssuerB, derPublicKeyB, serialNumberB) such that the concatenation of
    1.50 +// each triplet results in the same string of bytes but where each part in A is
    1.51 +// not equal to its counterpart in B. This is important because as a result it
    1.52 +// is computationally infeasible to find collisions that would subvert this
    1.53 +// cache (given that SHA384 is a cryptographically-secure hash function).
    1.54 +static SECStatus
    1.55 +CertIDHash(SHA384Buffer& buf, const CERTCertificate* aCert,
    1.56 +       const CERTCertificate* aIssuerCert)
    1.57 +{
    1.58 +  ScopedPK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
    1.59 +  if (!context) {
    1.60 +    return SECFailure;
    1.61 +  }
    1.62 +  SECStatus rv = PK11_DigestBegin(context.get());
    1.63 +  if (rv != SECSuccess) {
    1.64 +    return rv;
    1.65 +  }
    1.66 +  rv = PK11_DigestOp(context.get(), aCert->derIssuer.data,
    1.67 +                     aCert->derIssuer.len);
    1.68 +  if (rv != SECSuccess) {
    1.69 +    return rv;
    1.70 +  }
    1.71 +  rv = PK11_DigestOp(context.get(), aIssuerCert->derPublicKey.data,
    1.72 +                     aIssuerCert->derPublicKey.len);
    1.73 +  if (rv != SECSuccess) {
    1.74 +    return rv;
    1.75 +  }
    1.76 +  rv = PK11_DigestOp(context.get(), aCert->serialNumber.data,
    1.77 +                     aCert->serialNumber.len);
    1.78 +  if (rv != SECSuccess) {
    1.79 +    return rv;
    1.80 +  }
    1.81 +  uint32_t outLen = 0;
    1.82 +  rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
    1.83 +  if (outLen != SHA384_LENGTH) {
    1.84 +    return SECFailure;
    1.85 +  }
    1.86 +  return rv;
    1.87 +}
    1.88 +
    1.89 +SECStatus
    1.90 +OCSPCache::Entry::Init(const CERTCertificate* aCert,
    1.91 +                       const CERTCertificate* aIssuerCert,
    1.92 +                       PRErrorCode aErrorCode,
    1.93 +                       PRTime aThisUpdate,
    1.94 +                       PRTime aValidThrough)
    1.95 +{
    1.96 +  mErrorCode = aErrorCode;
    1.97 +  mThisUpdate = aThisUpdate;
    1.98 +  mValidThrough = aValidThrough;
    1.99 +  return CertIDHash(mIDHash, aCert, aIssuerCert);
   1.100 +}
   1.101 +
   1.102 +OCSPCache::OCSPCache()
   1.103 +  : mMutex("OCSPCache-mutex")
   1.104 +{
   1.105 +}
   1.106 +
   1.107 +OCSPCache::~OCSPCache()
   1.108 +{
   1.109 +  Clear();
   1.110 +}
   1.111 +
   1.112 +// Returns -1 if no entry is found for the given (cert, issuer) pair.
   1.113 +int32_t
   1.114 +OCSPCache::FindInternal(const CERTCertificate* aCert,
   1.115 +                        const CERTCertificate* aIssuerCert,
   1.116 +                        const MutexAutoLock& /* aProofOfLock */)
   1.117 +{
   1.118 +  if (mEntries.length() == 0) {
   1.119 +    return -1;
   1.120 +  }
   1.121 +
   1.122 +  SHA384Buffer idHash;
   1.123 +  SECStatus rv = CertIDHash(idHash, aCert, aIssuerCert);
   1.124 +  if (rv != SECSuccess) {
   1.125 +    return -1;
   1.126 +  }
   1.127 +
   1.128 +  // mEntries is sorted with the most-recently-used entry at the end.
   1.129 +  // Thus, searching from the end will often be fastest.
   1.130 +  for (int32_t i = mEntries.length() - 1; i >= 0; i--) {
   1.131 +    if (memcmp(mEntries[i]->mIDHash, idHash, SHA384_LENGTH) == 0) {
   1.132 +      return i;
   1.133 +    }
   1.134 +  }
   1.135 +  return -1;
   1.136 +}
   1.137 +
   1.138 +void
   1.139 +OCSPCache::LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
   1.140 +                        const CERTCertificate* aIssuerCert)
   1.141 +{
   1.142 +#ifdef PR_LOGGING
   1.143 +  if (PR_LOG_TEST(gCertVerifierLog, PR_LOG_DEBUG)) {
   1.144 +    mozilla::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
   1.145 +      cn(CERT_GetCommonName(&aCert->subject));
   1.146 +    mozilla::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
   1.147 +      cnIssuer(CERT_GetCommonName(&aIssuerCert->subject));
   1.148 +    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, (aMessage, cn.get(), cnIssuer.get()));
   1.149 +  }
   1.150 +#endif
   1.151 +}
   1.152 +
   1.153 +void
   1.154 +OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
   1.155 +                                const MutexAutoLock& /* aProofOfLock */)
   1.156 +{
   1.157 +  Entry* entry = mEntries[aIndex];
   1.158 +  // Since mEntries is sorted with the most-recently-used entry at the end,
   1.159 +  // aIndex is likely to be near the end, so this is likely to be fast.
   1.160 +  mEntries.erase(mEntries.begin() + aIndex);
   1.161 +  mEntries.append(entry);
   1.162 +}
   1.163 +
   1.164 +bool
   1.165 +OCSPCache::Get(const CERTCertificate* aCert,
   1.166 +               const CERTCertificate* aIssuerCert,
   1.167 +               PRErrorCode& aErrorCode,
   1.168 +               PRTime& aValidThrough)
   1.169 +{
   1.170 +  PR_ASSERT(aCert);
   1.171 +  PR_ASSERT(aIssuerCert);
   1.172 +
   1.173 +  MutexAutoLock lock(mMutex);
   1.174 +
   1.175 +  int32_t index = FindInternal(aCert, aIssuerCert, lock);
   1.176 +  if (index < 0) {
   1.177 +    LogWithCerts("OCSPCache::Get(%s, %s) not in cache", aCert, aIssuerCert);
   1.178 +    return false;
   1.179 +  }
   1.180 +  LogWithCerts("OCSPCache::Get(%s, %s) in cache", aCert, aIssuerCert);
   1.181 +  aErrorCode = mEntries[index]->mErrorCode;
   1.182 +  aValidThrough = mEntries[index]->mValidThrough;
   1.183 +  MakeMostRecentlyUsed(index, lock);
   1.184 +  return true;
   1.185 +}
   1.186 +
   1.187 +SECStatus
   1.188 +OCSPCache::Put(const CERTCertificate* aCert,
   1.189 +               const CERTCertificate* aIssuerCert,
   1.190 +               PRErrorCode aErrorCode,
   1.191 +               PRTime aThisUpdate,
   1.192 +               PRTime aValidThrough)
   1.193 +{
   1.194 +  PR_ASSERT(aCert);
   1.195 +  PR_ASSERT(aIssuerCert);
   1.196 +
   1.197 +  MutexAutoLock lock(mMutex);
   1.198 +
   1.199 +  int32_t index = FindInternal(aCert, aIssuerCert, lock);
   1.200 +
   1.201 +  if (index >= 0) {
   1.202 +    // Never replace an entry indicating a revoked certificate.
   1.203 +    if (mEntries[index]->mErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
   1.204 +      LogWithCerts("OCSPCache::Put(%s, %s) already in cache as revoked - "
   1.205 +                   "not replacing", aCert, aIssuerCert);
   1.206 +      MakeMostRecentlyUsed(index, lock);
   1.207 +      return SECSuccess;
   1.208 +    }
   1.209 +
   1.210 +    // Never replace a newer entry with an older one unless the older entry
   1.211 +    // indicates a revoked certificate, which we want to remember.
   1.212 +    if (mEntries[index]->mThisUpdate > aThisUpdate &&
   1.213 +        aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) {
   1.214 +      LogWithCerts("OCSPCache::Put(%s, %s) already in cache with more recent "
   1.215 +                   "validity - not replacing", aCert, aIssuerCert);
   1.216 +      MakeMostRecentlyUsed(index, lock);
   1.217 +      return SECSuccess;
   1.218 +    }
   1.219 +
   1.220 +    // Only known good responses or responses indicating an unknown
   1.221 +    // or revoked certificate should replace previously known responses.
   1.222 +    if (aErrorCode != 0 && aErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT &&
   1.223 +        aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) {
   1.224 +      LogWithCerts("OCSPCache::Put(%s, %s) already in cache - not replacing "
   1.225 +                   "with less important status", aCert, aIssuerCert);
   1.226 +      MakeMostRecentlyUsed(index, lock);
   1.227 +      return SECSuccess;
   1.228 +    }
   1.229 +
   1.230 +    LogWithCerts("OCSPCache::Put(%s, %s) already in cache - replacing",
   1.231 +                 aCert, aIssuerCert);
   1.232 +    mEntries[index]->mErrorCode = aErrorCode;
   1.233 +    mEntries[index]->mThisUpdate = aThisUpdate;
   1.234 +    mEntries[index]->mValidThrough = aValidThrough;
   1.235 +    MakeMostRecentlyUsed(index, lock);
   1.236 +    return SECSuccess;
   1.237 +  }
   1.238 +
   1.239 +  if (mEntries.length() == MaxEntries) {
   1.240 +    LogWithCerts("OCSPCache::Put(%s, %s) too full - evicting an entry", aCert,
   1.241 +                 aIssuerCert);
   1.242 +    for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
   1.243 +         toEvict++) {
   1.244 +      // Never evict an entry that indicates a revoked or unknokwn certificate,
   1.245 +      // because revoked responses are more security-critical to remember.
   1.246 +      if ((*toEvict)->mErrorCode != SEC_ERROR_REVOKED_CERTIFICATE &&
   1.247 +          (*toEvict)->mErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT) {
   1.248 +        delete *toEvict;
   1.249 +        mEntries.erase(toEvict);
   1.250 +        break;
   1.251 +      }
   1.252 +    }
   1.253 +    // Well, we tried, but apparently everything is revoked or unknown.
   1.254 +    // We don't want to remove a cached revoked or unknown response. If we're
   1.255 +    // trying to insert a good response, we can just return "successfully"
   1.256 +    // without doing so. This means we'll lose some speed, but it's not a
   1.257 +    // security issue. If we're trying to insert a revoked or unknown response,
   1.258 +    // we can't. We should return with an error that causes the current
   1.259 +    // verification to fail.
   1.260 +    if (mEntries.length() == MaxEntries) {
   1.261 +      if (aErrorCode != 0) {
   1.262 +        PR_SetError(aErrorCode, 0);
   1.263 +        return SECFailure;
   1.264 +      }
   1.265 +      return SECSuccess;
   1.266 +    }
   1.267 +  }
   1.268 +
   1.269 +  Entry* newEntry = new Entry();
   1.270 +  // Normally we don't have to do this in Gecko, because OOM is fatal.
   1.271 +  // However, if we want to embed this in another project, OOM might not
   1.272 +  // be fatal, so handle this case.
   1.273 +  if (!newEntry) {
   1.274 +    PR_SetError(SEC_ERROR_NO_MEMORY, 0);
   1.275 +    return SECFailure;
   1.276 +  }
   1.277 +  SECStatus rv = newEntry->Init(aCert, aIssuerCert, aErrorCode, aThisUpdate,
   1.278 +                                aValidThrough);
   1.279 +  if (rv != SECSuccess) {
   1.280 +    return rv;
   1.281 +  }
   1.282 +  mEntries.append(newEntry);
   1.283 +  LogWithCerts("OCSPCache::Put(%s, %s) added to cache", aCert, aIssuerCert);
   1.284 +  return SECSuccess;
   1.285 +}
   1.286 +
   1.287 +void
   1.288 +OCSPCache::Clear()
   1.289 +{
   1.290 +  MutexAutoLock lock(mMutex);
   1.291 +  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("OCSPCache::Clear: clearing cache"));
   1.292 +  // First go through and delete the memory being pointed to by the pointers
   1.293 +  // in the vector.
   1.294 +  for (Entry** entry = mEntries.begin(); entry < mEntries.end();
   1.295 +       entry++) {
   1.296 +    delete *entry;
   1.297 +  }
   1.298 +  // Then remove the pointers themselves.
   1.299 +  mEntries.clearAndFree();
   1.300 +}
   1.301 +
   1.302 +} } // namespace mozilla::psm

mercurial