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