security/certverifier/OCSPCache.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
michael@0 3 /* Copyright 2013 Mozilla Foundation
michael@0 4 *
michael@0 5 * Licensed under the Apache License, Version 2.0 (the "License");
michael@0 6 * you may not use this file except in compliance with the License.
michael@0 7 * You may obtain a copy of the License at
michael@0 8 *
michael@0 9 * http://www.apache.org/licenses/LICENSE-2.0
michael@0 10 *
michael@0 11 * Unless required by applicable law or agreed to in writing, software
michael@0 12 * distributed under the License is distributed on an "AS IS" BASIS,
michael@0 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
michael@0 14 * See the License for the specific language governing permissions and
michael@0 15 * limitations under the License.
michael@0 16 */
michael@0 17
michael@0 18 #include "OCSPCache.h"
michael@0 19
michael@0 20 #include "NSSCertDBTrustDomain.h"
michael@0 21 #include "pk11pub.h"
michael@0 22 #include "secerr.h"
michael@0 23
michael@0 24 #ifdef PR_LOGGING
michael@0 25 extern PRLogModuleInfo* gCertVerifierLog;
michael@0 26 #endif
michael@0 27
michael@0 28 namespace mozilla { namespace psm {
michael@0 29
michael@0 30 void
michael@0 31 MozillaPKIX_PK11_DestroyContext_true(PK11Context* context)
michael@0 32 {
michael@0 33 PK11_DestroyContext(context, true);
michael@0 34 }
michael@0 35
michael@0 36 typedef mozilla::pkix::ScopedPtr<PK11Context,
michael@0 37 MozillaPKIX_PK11_DestroyContext_true>
michael@0 38 ScopedPK11Context;
michael@0 39
michael@0 40 // Let derIssuer be the DER encoding of the issuer of aCert.
michael@0 41 // Let derPublicKey be the DER encoding of the public key of aIssuerCert.
michael@0 42 // Let serialNumber be the bytes of the serial number of aCert.
michael@0 43 // The value calculated is SHA384(derIssuer || derPublicKey || serialNumber).
michael@0 44 // Because the DER encodings include the length of the data encoded,
michael@0 45 // there do not exist A(derIssuerA, derPublicKeyA, serialNumberA) and
michael@0 46 // B(derIssuerB, derPublicKeyB, serialNumberB) such that the concatenation of
michael@0 47 // each triplet results in the same string of bytes but where each part in A is
michael@0 48 // not equal to its counterpart in B. This is important because as a result it
michael@0 49 // is computationally infeasible to find collisions that would subvert this
michael@0 50 // cache (given that SHA384 is a cryptographically-secure hash function).
michael@0 51 static SECStatus
michael@0 52 CertIDHash(SHA384Buffer& buf, const CERTCertificate* aCert,
michael@0 53 const CERTCertificate* aIssuerCert)
michael@0 54 {
michael@0 55 ScopedPK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
michael@0 56 if (!context) {
michael@0 57 return SECFailure;
michael@0 58 }
michael@0 59 SECStatus rv = PK11_DigestBegin(context.get());
michael@0 60 if (rv != SECSuccess) {
michael@0 61 return rv;
michael@0 62 }
michael@0 63 rv = PK11_DigestOp(context.get(), aCert->derIssuer.data,
michael@0 64 aCert->derIssuer.len);
michael@0 65 if (rv != SECSuccess) {
michael@0 66 return rv;
michael@0 67 }
michael@0 68 rv = PK11_DigestOp(context.get(), aIssuerCert->derPublicKey.data,
michael@0 69 aIssuerCert->derPublicKey.len);
michael@0 70 if (rv != SECSuccess) {
michael@0 71 return rv;
michael@0 72 }
michael@0 73 rv = PK11_DigestOp(context.get(), aCert->serialNumber.data,
michael@0 74 aCert->serialNumber.len);
michael@0 75 if (rv != SECSuccess) {
michael@0 76 return rv;
michael@0 77 }
michael@0 78 uint32_t outLen = 0;
michael@0 79 rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
michael@0 80 if (outLen != SHA384_LENGTH) {
michael@0 81 return SECFailure;
michael@0 82 }
michael@0 83 return rv;
michael@0 84 }
michael@0 85
michael@0 86 SECStatus
michael@0 87 OCSPCache::Entry::Init(const CERTCertificate* aCert,
michael@0 88 const CERTCertificate* aIssuerCert,
michael@0 89 PRErrorCode aErrorCode,
michael@0 90 PRTime aThisUpdate,
michael@0 91 PRTime aValidThrough)
michael@0 92 {
michael@0 93 mErrorCode = aErrorCode;
michael@0 94 mThisUpdate = aThisUpdate;
michael@0 95 mValidThrough = aValidThrough;
michael@0 96 return CertIDHash(mIDHash, aCert, aIssuerCert);
michael@0 97 }
michael@0 98
michael@0 99 OCSPCache::OCSPCache()
michael@0 100 : mMutex("OCSPCache-mutex")
michael@0 101 {
michael@0 102 }
michael@0 103
michael@0 104 OCSPCache::~OCSPCache()
michael@0 105 {
michael@0 106 Clear();
michael@0 107 }
michael@0 108
michael@0 109 // Returns -1 if no entry is found for the given (cert, issuer) pair.
michael@0 110 int32_t
michael@0 111 OCSPCache::FindInternal(const CERTCertificate* aCert,
michael@0 112 const CERTCertificate* aIssuerCert,
michael@0 113 const MutexAutoLock& /* aProofOfLock */)
michael@0 114 {
michael@0 115 if (mEntries.length() == 0) {
michael@0 116 return -1;
michael@0 117 }
michael@0 118
michael@0 119 SHA384Buffer idHash;
michael@0 120 SECStatus rv = CertIDHash(idHash, aCert, aIssuerCert);
michael@0 121 if (rv != SECSuccess) {
michael@0 122 return -1;
michael@0 123 }
michael@0 124
michael@0 125 // mEntries is sorted with the most-recently-used entry at the end.
michael@0 126 // Thus, searching from the end will often be fastest.
michael@0 127 for (int32_t i = mEntries.length() - 1; i >= 0; i--) {
michael@0 128 if (memcmp(mEntries[i]->mIDHash, idHash, SHA384_LENGTH) == 0) {
michael@0 129 return i;
michael@0 130 }
michael@0 131 }
michael@0 132 return -1;
michael@0 133 }
michael@0 134
michael@0 135 void
michael@0 136 OCSPCache::LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
michael@0 137 const CERTCertificate* aIssuerCert)
michael@0 138 {
michael@0 139 #ifdef PR_LOGGING
michael@0 140 if (PR_LOG_TEST(gCertVerifierLog, PR_LOG_DEBUG)) {
michael@0 141 mozilla::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
michael@0 142 cn(CERT_GetCommonName(&aCert->subject));
michael@0 143 mozilla::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
michael@0 144 cnIssuer(CERT_GetCommonName(&aIssuerCert->subject));
michael@0 145 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, (aMessage, cn.get(), cnIssuer.get()));
michael@0 146 }
michael@0 147 #endif
michael@0 148 }
michael@0 149
michael@0 150 void
michael@0 151 OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
michael@0 152 const MutexAutoLock& /* aProofOfLock */)
michael@0 153 {
michael@0 154 Entry* entry = mEntries[aIndex];
michael@0 155 // Since mEntries is sorted with the most-recently-used entry at the end,
michael@0 156 // aIndex is likely to be near the end, so this is likely to be fast.
michael@0 157 mEntries.erase(mEntries.begin() + aIndex);
michael@0 158 mEntries.append(entry);
michael@0 159 }
michael@0 160
michael@0 161 bool
michael@0 162 OCSPCache::Get(const CERTCertificate* aCert,
michael@0 163 const CERTCertificate* aIssuerCert,
michael@0 164 PRErrorCode& aErrorCode,
michael@0 165 PRTime& aValidThrough)
michael@0 166 {
michael@0 167 PR_ASSERT(aCert);
michael@0 168 PR_ASSERT(aIssuerCert);
michael@0 169
michael@0 170 MutexAutoLock lock(mMutex);
michael@0 171
michael@0 172 int32_t index = FindInternal(aCert, aIssuerCert, lock);
michael@0 173 if (index < 0) {
michael@0 174 LogWithCerts("OCSPCache::Get(%s, %s) not in cache", aCert, aIssuerCert);
michael@0 175 return false;
michael@0 176 }
michael@0 177 LogWithCerts("OCSPCache::Get(%s, %s) in cache", aCert, aIssuerCert);
michael@0 178 aErrorCode = mEntries[index]->mErrorCode;
michael@0 179 aValidThrough = mEntries[index]->mValidThrough;
michael@0 180 MakeMostRecentlyUsed(index, lock);
michael@0 181 return true;
michael@0 182 }
michael@0 183
michael@0 184 SECStatus
michael@0 185 OCSPCache::Put(const CERTCertificate* aCert,
michael@0 186 const CERTCertificate* aIssuerCert,
michael@0 187 PRErrorCode aErrorCode,
michael@0 188 PRTime aThisUpdate,
michael@0 189 PRTime aValidThrough)
michael@0 190 {
michael@0 191 PR_ASSERT(aCert);
michael@0 192 PR_ASSERT(aIssuerCert);
michael@0 193
michael@0 194 MutexAutoLock lock(mMutex);
michael@0 195
michael@0 196 int32_t index = FindInternal(aCert, aIssuerCert, lock);
michael@0 197
michael@0 198 if (index >= 0) {
michael@0 199 // Never replace an entry indicating a revoked certificate.
michael@0 200 if (mEntries[index]->mErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
michael@0 201 LogWithCerts("OCSPCache::Put(%s, %s) already in cache as revoked - "
michael@0 202 "not replacing", aCert, aIssuerCert);
michael@0 203 MakeMostRecentlyUsed(index, lock);
michael@0 204 return SECSuccess;
michael@0 205 }
michael@0 206
michael@0 207 // Never replace a newer entry with an older one unless the older entry
michael@0 208 // indicates a revoked certificate, which we want to remember.
michael@0 209 if (mEntries[index]->mThisUpdate > aThisUpdate &&
michael@0 210 aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) {
michael@0 211 LogWithCerts("OCSPCache::Put(%s, %s) already in cache with more recent "
michael@0 212 "validity - not replacing", aCert, aIssuerCert);
michael@0 213 MakeMostRecentlyUsed(index, lock);
michael@0 214 return SECSuccess;
michael@0 215 }
michael@0 216
michael@0 217 // Only known good responses or responses indicating an unknown
michael@0 218 // or revoked certificate should replace previously known responses.
michael@0 219 if (aErrorCode != 0 && aErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT &&
michael@0 220 aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) {
michael@0 221 LogWithCerts("OCSPCache::Put(%s, %s) already in cache - not replacing "
michael@0 222 "with less important status", aCert, aIssuerCert);
michael@0 223 MakeMostRecentlyUsed(index, lock);
michael@0 224 return SECSuccess;
michael@0 225 }
michael@0 226
michael@0 227 LogWithCerts("OCSPCache::Put(%s, %s) already in cache - replacing",
michael@0 228 aCert, aIssuerCert);
michael@0 229 mEntries[index]->mErrorCode = aErrorCode;
michael@0 230 mEntries[index]->mThisUpdate = aThisUpdate;
michael@0 231 mEntries[index]->mValidThrough = aValidThrough;
michael@0 232 MakeMostRecentlyUsed(index, lock);
michael@0 233 return SECSuccess;
michael@0 234 }
michael@0 235
michael@0 236 if (mEntries.length() == MaxEntries) {
michael@0 237 LogWithCerts("OCSPCache::Put(%s, %s) too full - evicting an entry", aCert,
michael@0 238 aIssuerCert);
michael@0 239 for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
michael@0 240 toEvict++) {
michael@0 241 // Never evict an entry that indicates a revoked or unknokwn certificate,
michael@0 242 // because revoked responses are more security-critical to remember.
michael@0 243 if ((*toEvict)->mErrorCode != SEC_ERROR_REVOKED_CERTIFICATE &&
michael@0 244 (*toEvict)->mErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT) {
michael@0 245 delete *toEvict;
michael@0 246 mEntries.erase(toEvict);
michael@0 247 break;
michael@0 248 }
michael@0 249 }
michael@0 250 // Well, we tried, but apparently everything is revoked or unknown.
michael@0 251 // We don't want to remove a cached revoked or unknown response. If we're
michael@0 252 // trying to insert a good response, we can just return "successfully"
michael@0 253 // without doing so. This means we'll lose some speed, but it's not a
michael@0 254 // security issue. If we're trying to insert a revoked or unknown response,
michael@0 255 // we can't. We should return with an error that causes the current
michael@0 256 // verification to fail.
michael@0 257 if (mEntries.length() == MaxEntries) {
michael@0 258 if (aErrorCode != 0) {
michael@0 259 PR_SetError(aErrorCode, 0);
michael@0 260 return SECFailure;
michael@0 261 }
michael@0 262 return SECSuccess;
michael@0 263 }
michael@0 264 }
michael@0 265
michael@0 266 Entry* newEntry = new Entry();
michael@0 267 // Normally we don't have to do this in Gecko, because OOM is fatal.
michael@0 268 // However, if we want to embed this in another project, OOM might not
michael@0 269 // be fatal, so handle this case.
michael@0 270 if (!newEntry) {
michael@0 271 PR_SetError(SEC_ERROR_NO_MEMORY, 0);
michael@0 272 return SECFailure;
michael@0 273 }
michael@0 274 SECStatus rv = newEntry->Init(aCert, aIssuerCert, aErrorCode, aThisUpdate,
michael@0 275 aValidThrough);
michael@0 276 if (rv != SECSuccess) {
michael@0 277 return rv;
michael@0 278 }
michael@0 279 mEntries.append(newEntry);
michael@0 280 LogWithCerts("OCSPCache::Put(%s, %s) added to cache", aCert, aIssuerCert);
michael@0 281 return SECSuccess;
michael@0 282 }
michael@0 283
michael@0 284 void
michael@0 285 OCSPCache::Clear()
michael@0 286 {
michael@0 287 MutexAutoLock lock(mMutex);
michael@0 288 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("OCSPCache::Clear: clearing cache"));
michael@0 289 // First go through and delete the memory being pointed to by the pointers
michael@0 290 // in the vector.
michael@0 291 for (Entry** entry = mEntries.begin(); entry < mEntries.end();
michael@0 292 entry++) {
michael@0 293 delete *entry;
michael@0 294 }
michael@0 295 // Then remove the pointers themselves.
michael@0 296 mEntries.clearAndFree();
michael@0 297 }
michael@0 298
michael@0 299 } } // namespace mozilla::psm

mercurial