Coverage Report

Created: 2018-09-25 14:53

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