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
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/.
10 /* Copyright 2013 Mozilla Contributors
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
16 * http://www.apache.org/licenses/LICENSE-2.0
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.
25 #include "OCSPCache.h"
29 #include "NSSCertDBTrustDomain.h"
31 #include "pkix/pkixnss.h"
32 #include "ScopedNSSTypes.h"
36 extern PRLogModuleInfo
* gCertVerifierLog
;
39 using namespace mozilla::pkix
;
41 namespace mozilla
{ namespace psm
{
43 // Let derIssuer be the DER encoding of the issuer of aCert.
44 // Let derPublicKey be the DER encoding of the public key of aIssuerCert.
45 // Let serialNumber be the bytes of the serial number of aCert.
46 // The value calculated is SHA384(derIssuer || derPublicKey || serialNumber).
47 // Because the DER encodings include the length of the data encoded,
48 // there do not exist A(derIssuerA, derPublicKeyA, serialNumberA) and
49 // B(derIssuerB, derPublicKeyB, serialNumberB) such that the concatenation of
50 // each triplet results in the same string of bytes but where each part in A is
51 // not equal to its counterpart in B. This is important because as a result it
52 // is computationally infeasible to find collisions that would subvert this
53 // cache (given that SHA384 is a cryptographically-secure hash function).
55 CertIDHash(SHA384Buffer
& buf
, const CertID
& certID
)
57 ScopedPK11Context
context(PK11_CreateDigestContext(SEC_OID_SHA384
));
61 SECStatus rv
= PK11_DigestBegin(context
.get());
62 if (rv
!= SECSuccess
) {
65 SECItem certIDIssuer
= UnsafeMapInputToSECItem(certID
.issuer
);
66 rv
= PK11_DigestOp(context
.get(), certIDIssuer
.data
, certIDIssuer
.len
);
67 if (rv
!= SECSuccess
) {
70 SECItem certIDIssuerSubjectPublicKeyInfo
=
71 UnsafeMapInputToSECItem(certID
.issuerSubjectPublicKeyInfo
);
72 rv
= PK11_DigestOp(context
.get(), certIDIssuerSubjectPublicKeyInfo
.data
,
73 certIDIssuerSubjectPublicKeyInfo
.len
);
74 if (rv
!= SECSuccess
) {
77 SECItem certIDSerialNumber
=
78 UnsafeMapInputToSECItem(certID
.serialNumber
);
79 rv
= PK11_DigestOp(context
.get(), certIDSerialNumber
.data
,
80 certIDSerialNumber
.len
);
81 if (rv
!= SECSuccess
) {
85 rv
= PK11_DigestFinal(context
.get(), buf
, &outLen
, SHA384_LENGTH
);
86 if (outLen
!= SHA384_LENGTH
) {
93 OCSPCache::Entry::Init(const CertID
& aCertID
)
95 SECStatus srv
= CertIDHash(mIDHash
, aCertID
);
96 if (srv
!= SECSuccess
) {
97 return MapPRErrorCodeToResult(PR_GetError());
102 OCSPCache::OCSPCache()
103 : mMutex("OCSPCache-mutex")
107 OCSPCache::~OCSPCache()
112 // Returns false with index in an undefined state if no matching entry was
115 OCSPCache::FindInternal(const CertID
& aCertID
, /*out*/ size_t& index
,
116 const MutexAutoLock
& /* aProofOfLock */)
118 if (mEntries
.length() == 0) {
123 SECStatus rv
= CertIDHash(idHash
, aCertID
);
124 if (rv
!= SECSuccess
) {
128 // mEntries is sorted with the most-recently-used entry at the end.
129 // Thus, searching from the end will often be fastest.
130 index
= mEntries
.length();
133 if (memcmp(mEntries
[index
]->mIDHash
, idHash
, SHA384_LENGTH
) == 0) {
141 LogWithCertID(const char* aMessage
, const CertID
& aCertID
)
143 PR_LOG(gCertVerifierLog
, PR_LOG_DEBUG
, (aMessage
, &aCertID
));
147 OCSPCache::MakeMostRecentlyUsed(size_t aIndex
,
148 const MutexAutoLock
& /* aProofOfLock */)
150 Entry
* entry
= mEntries
[aIndex
];
151 // Since mEntries is sorted with the most-recently-used entry at the end,
152 // aIndex is likely to be near the end, so this is likely to be fast.
153 mEntries
.erase(mEntries
.begin() + aIndex
);
154 mEntries
.append(entry
);
158 OCSPCache::Get(const CertID
& aCertID
, Result
& aResult
, Time
& aValidThrough
)
160 MutexAutoLock
lock(mMutex
);
163 if (!FindInternal(aCertID
, index
, lock
)) {
164 LogWithCertID("OCSPCache::Get(%p) not in cache", aCertID
);
167 LogWithCertID("OCSPCache::Get(%p) in cache", aCertID
);
168 aResult
= mEntries
[index
]->mResult
;
169 aValidThrough
= mEntries
[index
]->mValidThrough
;
170 MakeMostRecentlyUsed(index
, lock
);
175 OCSPCache::Put(const CertID
& aCertID
, Result aResult
,
176 Time aThisUpdate
, Time aValidThrough
)
178 MutexAutoLock
lock(mMutex
);
181 if (FindInternal(aCertID
, index
, lock
)) {
182 // Never replace an entry indicating a revoked certificate.
183 if (mEntries
[index
]->mResult
== Result::ERROR_REVOKED_CERTIFICATE
) {
184 LogWithCertID("OCSPCache::Put(%p) already in cache as revoked - "
185 "not replacing", aCertID
);
186 MakeMostRecentlyUsed(index
, lock
);
190 // Never replace a newer entry with an older one unless the older entry
191 // indicates a revoked certificate, which we want to remember.
192 if (mEntries
[index
]->mThisUpdate
> aThisUpdate
&&
193 aResult
!= Result::ERROR_REVOKED_CERTIFICATE
) {
194 LogWithCertID("OCSPCache::Put(%p) already in cache with more recent "
195 "validity - not replacing", aCertID
);
196 MakeMostRecentlyUsed(index
, lock
);
200 // Only known good responses or responses indicating an unknown
201 // or revoked certificate should replace previously known responses.
202 if (aResult
!= Success
&&
203 aResult
!= Result::ERROR_OCSP_UNKNOWN_CERT
&&
204 aResult
!= Result::ERROR_REVOKED_CERTIFICATE
) {
205 LogWithCertID("OCSPCache::Put(%p) already in cache - not replacing "
206 "with less important status", aCertID
);
207 MakeMostRecentlyUsed(index
, lock
);
211 LogWithCertID("OCSPCache::Put(%p) already in cache - replacing", aCertID
);
212 mEntries
[index
]->mResult
= aResult
;
213 mEntries
[index
]->mThisUpdate
= aThisUpdate
;
214 mEntries
[index
]->mValidThrough
= aValidThrough
;
215 MakeMostRecentlyUsed(index
, lock
);
219 if (mEntries
.length() == MaxEntries
) {
220 LogWithCertID("OCSPCache::Put(%p) too full - evicting an entry", aCertID
);
221 for (Entry
** toEvict
= mEntries
.begin(); toEvict
!= mEntries
.end();
223 // Never evict an entry that indicates a revoked or unknokwn certificate,
224 // because revoked responses are more security-critical to remember.
225 if ((*toEvict
)->mResult
!= Result::ERROR_REVOKED_CERTIFICATE
&&
226 (*toEvict
)->mResult
!= Result::ERROR_OCSP_UNKNOWN_CERT
) {
228 mEntries
.erase(toEvict
);
232 // Well, we tried, but apparently everything is revoked or unknown.
233 // We don't want to remove a cached revoked or unknown response. If we're
234 // trying to insert a good response, we can just return "successfully"
235 // without doing so. This means we'll lose some speed, but it's not a
236 // security issue. If we're trying to insert a revoked or unknown response,
237 // we can't. We should return with an error that causes the current
238 // verification to fail.
239 if (mEntries
.length() == MaxEntries
) {
244 Entry
* newEntry
= new (std::nothrow
) Entry(aResult
, aThisUpdate
,
246 // Normally we don't have to do this in Gecko, because OOM is fatal.
247 // However, if we want to embed this in another project, OOM might not
248 // be fatal, so handle this case.
250 return Result::FATAL_ERROR_NO_MEMORY
;
252 Result rv
= newEntry
->Init(aCertID
);
257 mEntries
.append(newEntry
);
258 LogWithCertID("OCSPCache::Put(%p) added to cache", aCertID
);
265 MutexAutoLock
lock(mMutex
);
266 PR_LOG(gCertVerifierLog
, PR_LOG_DEBUG
, ("OCSPCache::Clear: clearing cache"));
267 // First go through and delete the memory being pointed to by the pointers
269 for (Entry
** entry
= mEntries
.begin(); entry
< mEntries
.end();
273 // Then remove the pointers themselves.
274 mEntries
.clearAndFree();
277 } } // namespace mozilla::psm