michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* Copyright 2013 Mozilla Foundation michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include "OCSPCache.h" michael@0: michael@0: #include "NSSCertDBTrustDomain.h" michael@0: #include "pk11pub.h" michael@0: #include "secerr.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gCertVerifierLog; michael@0: #endif michael@0: michael@0: namespace mozilla { namespace psm { michael@0: michael@0: void michael@0: MozillaPKIX_PK11_DestroyContext_true(PK11Context* context) michael@0: { michael@0: PK11_DestroyContext(context, true); michael@0: } michael@0: michael@0: typedef mozilla::pkix::ScopedPtr michael@0: ScopedPK11Context; michael@0: michael@0: // Let derIssuer be the DER encoding of the issuer of aCert. michael@0: // Let derPublicKey be the DER encoding of the public key of aIssuerCert. michael@0: // Let serialNumber be the bytes of the serial number of aCert. michael@0: // The value calculated is SHA384(derIssuer || derPublicKey || serialNumber). michael@0: // Because the DER encodings include the length of the data encoded, michael@0: // there do not exist A(derIssuerA, derPublicKeyA, serialNumberA) and michael@0: // B(derIssuerB, derPublicKeyB, serialNumberB) such that the concatenation of michael@0: // each triplet results in the same string of bytes but where each part in A is michael@0: // not equal to its counterpart in B. This is important because as a result it michael@0: // is computationally infeasible to find collisions that would subvert this michael@0: // cache (given that SHA384 is a cryptographically-secure hash function). michael@0: static SECStatus michael@0: CertIDHash(SHA384Buffer& buf, const CERTCertificate* aCert, michael@0: const CERTCertificate* aIssuerCert) michael@0: { michael@0: ScopedPK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384)); michael@0: if (!context) { michael@0: return SECFailure; michael@0: } michael@0: SECStatus rv = PK11_DigestBegin(context.get()); michael@0: if (rv != SECSuccess) { michael@0: return rv; michael@0: } michael@0: rv = PK11_DigestOp(context.get(), aCert->derIssuer.data, michael@0: aCert->derIssuer.len); michael@0: if (rv != SECSuccess) { michael@0: return rv; michael@0: } michael@0: rv = PK11_DigestOp(context.get(), aIssuerCert->derPublicKey.data, michael@0: aIssuerCert->derPublicKey.len); michael@0: if (rv != SECSuccess) { michael@0: return rv; michael@0: } michael@0: rv = PK11_DigestOp(context.get(), aCert->serialNumber.data, michael@0: aCert->serialNumber.len); michael@0: if (rv != SECSuccess) { michael@0: return rv; michael@0: } michael@0: uint32_t outLen = 0; michael@0: rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH); michael@0: if (outLen != SHA384_LENGTH) { michael@0: return SECFailure; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: SECStatus michael@0: OCSPCache::Entry::Init(const CERTCertificate* aCert, michael@0: const CERTCertificate* aIssuerCert, michael@0: PRErrorCode aErrorCode, michael@0: PRTime aThisUpdate, michael@0: PRTime aValidThrough) michael@0: { michael@0: mErrorCode = aErrorCode; michael@0: mThisUpdate = aThisUpdate; michael@0: mValidThrough = aValidThrough; michael@0: return CertIDHash(mIDHash, aCert, aIssuerCert); michael@0: } michael@0: michael@0: OCSPCache::OCSPCache() michael@0: : mMutex("OCSPCache-mutex") michael@0: { michael@0: } michael@0: michael@0: OCSPCache::~OCSPCache() michael@0: { michael@0: Clear(); michael@0: } michael@0: michael@0: // Returns -1 if no entry is found for the given (cert, issuer) pair. michael@0: int32_t michael@0: OCSPCache::FindInternal(const CERTCertificate* aCert, michael@0: const CERTCertificate* aIssuerCert, michael@0: const MutexAutoLock& /* aProofOfLock */) michael@0: { michael@0: if (mEntries.length() == 0) { michael@0: return -1; michael@0: } michael@0: michael@0: SHA384Buffer idHash; michael@0: SECStatus rv = CertIDHash(idHash, aCert, aIssuerCert); michael@0: if (rv != SECSuccess) { michael@0: return -1; michael@0: } michael@0: michael@0: // mEntries is sorted with the most-recently-used entry at the end. michael@0: // Thus, searching from the end will often be fastest. michael@0: for (int32_t i = mEntries.length() - 1; i >= 0; i--) { michael@0: if (memcmp(mEntries[i]->mIDHash, idHash, SHA384_LENGTH) == 0) { michael@0: return i; michael@0: } michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: void michael@0: OCSPCache::LogWithCerts(const char* aMessage, const CERTCertificate* aCert, michael@0: const CERTCertificate* aIssuerCert) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gCertVerifierLog, PR_LOG_DEBUG)) { michael@0: mozilla::pkix::ScopedPtr michael@0: cn(CERT_GetCommonName(&aCert->subject)); michael@0: mozilla::pkix::ScopedPtr michael@0: cnIssuer(CERT_GetCommonName(&aIssuerCert->subject)); michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, (aMessage, cn.get(), cnIssuer.get())); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: OCSPCache::MakeMostRecentlyUsed(size_t aIndex, michael@0: const MutexAutoLock& /* aProofOfLock */) michael@0: { michael@0: Entry* entry = mEntries[aIndex]; michael@0: // Since mEntries is sorted with the most-recently-used entry at the end, michael@0: // aIndex is likely to be near the end, so this is likely to be fast. michael@0: mEntries.erase(mEntries.begin() + aIndex); michael@0: mEntries.append(entry); michael@0: } michael@0: michael@0: bool michael@0: OCSPCache::Get(const CERTCertificate* aCert, michael@0: const CERTCertificate* aIssuerCert, michael@0: PRErrorCode& aErrorCode, michael@0: PRTime& aValidThrough) michael@0: { michael@0: PR_ASSERT(aCert); michael@0: PR_ASSERT(aIssuerCert); michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: int32_t index = FindInternal(aCert, aIssuerCert, lock); michael@0: if (index < 0) { michael@0: LogWithCerts("OCSPCache::Get(%s, %s) not in cache", aCert, aIssuerCert); michael@0: return false; michael@0: } michael@0: LogWithCerts("OCSPCache::Get(%s, %s) in cache", aCert, aIssuerCert); michael@0: aErrorCode = mEntries[index]->mErrorCode; michael@0: aValidThrough = mEntries[index]->mValidThrough; michael@0: MakeMostRecentlyUsed(index, lock); michael@0: return true; michael@0: } michael@0: michael@0: SECStatus michael@0: OCSPCache::Put(const CERTCertificate* aCert, michael@0: const CERTCertificate* aIssuerCert, michael@0: PRErrorCode aErrorCode, michael@0: PRTime aThisUpdate, michael@0: PRTime aValidThrough) michael@0: { michael@0: PR_ASSERT(aCert); michael@0: PR_ASSERT(aIssuerCert); michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: int32_t index = FindInternal(aCert, aIssuerCert, lock); michael@0: michael@0: if (index >= 0) { michael@0: // Never replace an entry indicating a revoked certificate. michael@0: if (mEntries[index]->mErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) { michael@0: LogWithCerts("OCSPCache::Put(%s, %s) already in cache as revoked - " michael@0: "not replacing", aCert, aIssuerCert); michael@0: MakeMostRecentlyUsed(index, lock); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: // Never replace a newer entry with an older one unless the older entry michael@0: // indicates a revoked certificate, which we want to remember. michael@0: if (mEntries[index]->mThisUpdate > aThisUpdate && michael@0: aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) { michael@0: LogWithCerts("OCSPCache::Put(%s, %s) already in cache with more recent " michael@0: "validity - not replacing", aCert, aIssuerCert); michael@0: MakeMostRecentlyUsed(index, lock); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: // Only known good responses or responses indicating an unknown michael@0: // or revoked certificate should replace previously known responses. michael@0: if (aErrorCode != 0 && aErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT && michael@0: aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) { michael@0: LogWithCerts("OCSPCache::Put(%s, %s) already in cache - not replacing " michael@0: "with less important status", aCert, aIssuerCert); michael@0: MakeMostRecentlyUsed(index, lock); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: LogWithCerts("OCSPCache::Put(%s, %s) already in cache - replacing", michael@0: aCert, aIssuerCert); michael@0: mEntries[index]->mErrorCode = aErrorCode; michael@0: mEntries[index]->mThisUpdate = aThisUpdate; michael@0: mEntries[index]->mValidThrough = aValidThrough; michael@0: MakeMostRecentlyUsed(index, lock); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: if (mEntries.length() == MaxEntries) { michael@0: LogWithCerts("OCSPCache::Put(%s, %s) too full - evicting an entry", aCert, michael@0: aIssuerCert); michael@0: for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end(); michael@0: toEvict++) { michael@0: // Never evict an entry that indicates a revoked or unknokwn certificate, michael@0: // because revoked responses are more security-critical to remember. michael@0: if ((*toEvict)->mErrorCode != SEC_ERROR_REVOKED_CERTIFICATE && michael@0: (*toEvict)->mErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT) { michael@0: delete *toEvict; michael@0: mEntries.erase(toEvict); michael@0: break; michael@0: } michael@0: } michael@0: // Well, we tried, but apparently everything is revoked or unknown. michael@0: // We don't want to remove a cached revoked or unknown response. If we're michael@0: // trying to insert a good response, we can just return "successfully" michael@0: // without doing so. This means we'll lose some speed, but it's not a michael@0: // security issue. If we're trying to insert a revoked or unknown response, michael@0: // we can't. We should return with an error that causes the current michael@0: // verification to fail. michael@0: if (mEntries.length() == MaxEntries) { michael@0: if (aErrorCode != 0) { michael@0: PR_SetError(aErrorCode, 0); michael@0: return SECFailure; michael@0: } michael@0: return SECSuccess; michael@0: } michael@0: } michael@0: michael@0: Entry* newEntry = new Entry(); michael@0: // Normally we don't have to do this in Gecko, because OOM is fatal. michael@0: // However, if we want to embed this in another project, OOM might not michael@0: // be fatal, so handle this case. michael@0: if (!newEntry) { michael@0: PR_SetError(SEC_ERROR_NO_MEMORY, 0); michael@0: return SECFailure; michael@0: } michael@0: SECStatus rv = newEntry->Init(aCert, aIssuerCert, aErrorCode, aThisUpdate, michael@0: aValidThrough); michael@0: if (rv != SECSuccess) { michael@0: return rv; michael@0: } michael@0: mEntries.append(newEntry); michael@0: LogWithCerts("OCSPCache::Put(%s, %s) added to cache", aCert, aIssuerCert); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: void michael@0: OCSPCache::Clear() michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("OCSPCache::Clear: clearing cache")); michael@0: // First go through and delete the memory being pointed to by the pointers michael@0: // in the vector. michael@0: for (Entry** entry = mEntries.begin(); entry < mEntries.end(); michael@0: entry++) { michael@0: delete *entry; michael@0: } michael@0: // Then remove the pointers themselves. michael@0: mEntries.clearAndFree(); michael@0: } michael@0: michael@0: } } // namespace mozilla::psm