OCSPCache.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This code is made available to you under your choice of the following sets
  3. * of licensing terms:
  4. */
  5. /* This Source Code Form is subject to the terms of the Mozilla Public
  6. * License, v. 2.0. If a copy of the MPL was not distributed with this
  7. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  8. */
  9. /* Copyright 2013 Mozilla Contributors
  10. *
  11. * Licensed under the Apache License, Version 2.0 (the "License");
  12. * you may not use this file except in compliance with the License.
  13. * You may obtain a copy of the License at
  14. *
  15. * http://www.apache.org/licenses/LICENSE-2.0
  16. *
  17. * Unless required by applicable law or agreed to in writing, software
  18. * distributed under the License is distributed on an "AS IS" BASIS,
  19. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20. * See the License for the specific language governing permissions and
  21. * limitations under the License.
  22. */
  23. #include "OCSPCache.h"
  24. #include <limits>
  25. #include "NSSCertDBTrustDomain.h"
  26. #include "pk11pub.h"
  27. #include "pkix/pkixnss.h"
  28. #include "ScopedNSSTypes.h"
  29. #include "secerr.h"
  30. extern mozilla::LazyLogModule gCertVerifierLog;
  31. using namespace mozilla::pkix;
  32. namespace mozilla { namespace psm {
  33. typedef mozilla::pkix::Result Result;
  34. static SECStatus
  35. DigestLength(UniquePK11Context& context, uint32_t length)
  36. {
  37. // Restrict length to 2 bytes because it should be big enough for all
  38. // inputs this code will actually see and that it is well-defined and
  39. // type-size-independent.
  40. if (length >= 65536) {
  41. return SECFailure;
  42. }
  43. unsigned char array[2];
  44. array[0] = length & 255;
  45. array[1] = (length >> 8) & 255;
  46. return PK11_DigestOp(context.get(), array, MOZ_ARRAY_LENGTH(array));
  47. }
  48. // Let derIssuer be the DER encoding of the issuer of certID.
  49. // Let derPublicKey be the DER encoding of the public key of certID.
  50. // Let serialNumber be the bytes of the serial number of certID.
  51. // Let serialNumberLen be the number of bytes of serialNumber.
  52. // Let firstPartyDomain be the first party domain of originAttributes.
  53. // It is only non-empty when "privacy.firstParty.isolate" is enabled, in order
  54. // to isolate OCSP cache by first party.
  55. // Let firstPartyDomainLen be the number of bytes of firstPartyDomain.
  56. // The value calculated is SHA384(derIssuer || derPublicKey || serialNumberLen
  57. // || serialNumber || firstPartyDomainLen || firstPartyDomain).
  58. // Because the DER encodings include the length of the data encoded, and we also
  59. // include the length of serialNumber and originAttributes, there do not exist
  60. // A(derIssuerA, derPublicKeyA, serialNumberLenA, serialNumberA,
  61. // originAttributesLenA, originAttributesA) and B(derIssuerB, derPublicKeyB,
  62. // serialNumberLenB, serialNumberB, originAttributesLenB, originAttributesB)
  63. // such that the concatenation of each tuple results in the same string of
  64. // bytes but where each part in A is not equal to its counterpart in B. This is
  65. // important because as a result it is computationally infeasible to find
  66. // collisions that would subvert this cache (given that SHA384 is a
  67. // cryptographically-secure hash function).
  68. static SECStatus
  69. CertIDHash(SHA384Buffer& buf, const CertID& certID,
  70. const NeckoOriginAttributes& originAttributes)
  71. {
  72. UniquePK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
  73. if (!context) {
  74. return SECFailure;
  75. }
  76. SECStatus rv = PK11_DigestBegin(context.get());
  77. if (rv != SECSuccess) {
  78. return rv;
  79. }
  80. SECItem certIDIssuer = UnsafeMapInputToSECItem(certID.issuer);
  81. rv = PK11_DigestOp(context.get(), certIDIssuer.data, certIDIssuer.len);
  82. if (rv != SECSuccess) {
  83. return rv;
  84. }
  85. SECItem certIDIssuerSubjectPublicKeyInfo =
  86. UnsafeMapInputToSECItem(certID.issuerSubjectPublicKeyInfo);
  87. rv = PK11_DigestOp(context.get(), certIDIssuerSubjectPublicKeyInfo.data,
  88. certIDIssuerSubjectPublicKeyInfo.len);
  89. if (rv != SECSuccess) {
  90. return rv;
  91. }
  92. SECItem certIDSerialNumber =
  93. UnsafeMapInputToSECItem(certID.serialNumber);
  94. rv = DigestLength(context, certIDSerialNumber.len);
  95. if (rv != SECSuccess) {
  96. return rv;
  97. }
  98. rv = PK11_DigestOp(context.get(), certIDSerialNumber.data,
  99. certIDSerialNumber.len);
  100. if (rv != SECSuccess) {
  101. return rv;
  102. }
  103. // OCSP should not be isolated by containers.
  104. NS_ConvertUTF16toUTF8 firstPartyDomain(originAttributes.mFirstPartyDomain);
  105. if (!firstPartyDomain.IsEmpty()) {
  106. rv = DigestLength(context, firstPartyDomain.Length());
  107. if (rv != SECSuccess) {
  108. return rv;
  109. }
  110. rv = PK11_DigestOp(context.get(),
  111. BitwiseCast<const unsigned char*>(firstPartyDomain.get()),
  112. firstPartyDomain.Length());
  113. if (rv != SECSuccess) {
  114. return rv;
  115. }
  116. }
  117. uint32_t outLen = 0;
  118. rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
  119. if (outLen != SHA384_LENGTH) {
  120. return SECFailure;
  121. }
  122. return rv;
  123. }
  124. Result
  125. OCSPCache::Entry::Init(const CertID& aCertID,
  126. const NeckoOriginAttributes& aOriginAttributes)
  127. {
  128. SECStatus srv = CertIDHash(mIDHash, aCertID, aOriginAttributes);
  129. if (srv != SECSuccess) {
  130. return MapPRErrorCodeToResult(PR_GetError());
  131. }
  132. return Success;
  133. }
  134. OCSPCache::OCSPCache()
  135. : mMutex("OCSPCache-mutex")
  136. {
  137. }
  138. OCSPCache::~OCSPCache()
  139. {
  140. Clear();
  141. }
  142. // Returns false with index in an undefined state if no matching entry was
  143. // found.
  144. bool
  145. OCSPCache::FindInternal(const CertID& aCertID,
  146. const NeckoOriginAttributes& aOriginAttributes,
  147. /*out*/ size_t& index,
  148. const MutexAutoLock& /* aProofOfLock */)
  149. {
  150. if (mEntries.length() == 0) {
  151. return false;
  152. }
  153. SHA384Buffer idHash;
  154. SECStatus rv = CertIDHash(idHash, aCertID, aOriginAttributes);
  155. if (rv != SECSuccess) {
  156. return false;
  157. }
  158. // mEntries is sorted with the most-recently-used entry at the end.
  159. // Thus, searching from the end will often be fastest.
  160. index = mEntries.length();
  161. while (index > 0) {
  162. --index;
  163. if (memcmp(mEntries[index]->mIDHash, idHash, SHA384_LENGTH) == 0) {
  164. return true;
  165. }
  166. }
  167. return false;
  168. }
  169. static inline void
  170. LogWithCertID(const char* aMessage, const CertID& aCertID,
  171. const NeckoOriginAttributes& aOriginAttributes)
  172. {
  173. NS_ConvertUTF16toUTF8 firstPartyDomain(aOriginAttributes.mFirstPartyDomain);
  174. MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
  175. (aMessage, &aCertID, firstPartyDomain.get()));
  176. }
  177. void
  178. OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
  179. const MutexAutoLock& /* aProofOfLock */)
  180. {
  181. Entry* entry = mEntries[aIndex];
  182. // Since mEntries is sorted with the most-recently-used entry at the end,
  183. // aIndex is likely to be near the end, so this is likely to be fast.
  184. mEntries.erase(mEntries.begin() + aIndex);
  185. // erase() does not shrink or realloc memory, so the append below should
  186. // always succeed.
  187. MOZ_RELEASE_ASSERT(mEntries.append(entry));
  188. }
  189. bool
  190. OCSPCache::Get(const CertID& aCertID,
  191. const NeckoOriginAttributes& aOriginAttributes,
  192. Result& aResult, Time& aValidThrough)
  193. {
  194. MutexAutoLock lock(mMutex);
  195. size_t index;
  196. if (!FindInternal(aCertID, aOriginAttributes, index, lock)) {
  197. LogWithCertID("OCSPCache::Get(%p,\"%s\") not in cache", aCertID,
  198. aOriginAttributes);
  199. return false;
  200. }
  201. LogWithCertID("OCSPCache::Get(%p,\"%s\") in cache", aCertID,
  202. aOriginAttributes);
  203. aResult = mEntries[index]->mResult;
  204. aValidThrough = mEntries[index]->mValidThrough;
  205. MakeMostRecentlyUsed(index, lock);
  206. return true;
  207. }
  208. Result
  209. OCSPCache::Put(const CertID& aCertID,
  210. const NeckoOriginAttributes& aOriginAttributes,
  211. Result aResult, Time aThisUpdate, Time aValidThrough)
  212. {
  213. MutexAutoLock lock(mMutex);
  214. size_t index;
  215. if (FindInternal(aCertID, aOriginAttributes, index, lock)) {
  216. // Never replace an entry indicating a revoked certificate.
  217. if (mEntries[index]->mResult == Result::ERROR_REVOKED_CERTIFICATE) {
  218. LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache as revoked - "
  219. "not replacing", aCertID, aOriginAttributes);
  220. MakeMostRecentlyUsed(index, lock);
  221. return Success;
  222. }
  223. // Never replace a newer entry with an older one unless the older entry
  224. // indicates a revoked certificate, which we want to remember.
  225. if (mEntries[index]->mThisUpdate > aThisUpdate &&
  226. aResult != Result::ERROR_REVOKED_CERTIFICATE) {
  227. LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache with more "
  228. "recent validity - not replacing", aCertID,
  229. aOriginAttributes);
  230. MakeMostRecentlyUsed(index, lock);
  231. return Success;
  232. }
  233. // Only known good responses or responses indicating an unknown
  234. // or revoked certificate should replace previously known responses.
  235. if (aResult != Success &&
  236. aResult != Result::ERROR_OCSP_UNKNOWN_CERT &&
  237. aResult != Result::ERROR_REVOKED_CERTIFICATE) {
  238. LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - not "
  239. "replacing with less important status", aCertID,
  240. aOriginAttributes);
  241. MakeMostRecentlyUsed(index, lock);
  242. return Success;
  243. }
  244. LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - replacing",
  245. aCertID, aOriginAttributes);
  246. mEntries[index]->mResult = aResult;
  247. mEntries[index]->mThisUpdate = aThisUpdate;
  248. mEntries[index]->mValidThrough = aValidThrough;
  249. MakeMostRecentlyUsed(index, lock);
  250. return Success;
  251. }
  252. if (mEntries.length() == MaxEntries) {
  253. LogWithCertID("OCSPCache::Put(%p, \"%s\") too full - evicting an entry",
  254. aCertID, aOriginAttributes);
  255. for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
  256. toEvict++) {
  257. // Never evict an entry that indicates a revoked or unknokwn certificate,
  258. // because revoked responses are more security-critical to remember.
  259. if ((*toEvict)->mResult != Result::ERROR_REVOKED_CERTIFICATE &&
  260. (*toEvict)->mResult != Result::ERROR_OCSP_UNKNOWN_CERT) {
  261. delete *toEvict;
  262. mEntries.erase(toEvict);
  263. break;
  264. }
  265. }
  266. // Well, we tried, but apparently everything is revoked or unknown.
  267. // We don't want to remove a cached revoked or unknown response. If we're
  268. // trying to insert a good response, we can just return "successfully"
  269. // without doing so. This means we'll lose some speed, but it's not a
  270. // security issue. If we're trying to insert a revoked or unknown response,
  271. // we can't. We should return with an error that causes the current
  272. // verification to fail.
  273. if (mEntries.length() == MaxEntries) {
  274. return aResult;
  275. }
  276. }
  277. Entry* newEntry = new (std::nothrow) Entry(aResult, aThisUpdate,
  278. aValidThrough);
  279. // Normally we don't have to do this in Gecko, because OOM is fatal.
  280. // However, if we want to embed this in another project, OOM might not
  281. // be fatal, so handle this case.
  282. if (!newEntry) {
  283. return Result::FATAL_ERROR_NO_MEMORY;
  284. }
  285. Result rv = newEntry->Init(aCertID, aOriginAttributes);
  286. if (rv != Success) {
  287. delete newEntry;
  288. return rv;
  289. }
  290. if (!mEntries.append(newEntry)) {
  291. delete newEntry;
  292. return Result::FATAL_ERROR_NO_MEMORY;
  293. }
  294. LogWithCertID("OCSPCache::Put(%p, \"%s\") added to cache", aCertID,
  295. aOriginAttributes);
  296. return Success;
  297. }
  298. void
  299. OCSPCache::Clear()
  300. {
  301. MutexAutoLock lock(mMutex);
  302. MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("OCSPCache::Clear: clearing cache"));
  303. // First go through and delete the memory being pointed to by the pointers
  304. // in the vector.
  305. for (Entry** entry = mEntries.begin(); entry < mEntries.end();
  306. entry++) {
  307. delete *entry;
  308. }
  309. // Then remove the pointers themselves.
  310. mEntries.clearAndFree();
  311. }
  312. } } // namespace mozilla::psm