Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "SSLTokensCache.h"
6
#include "mozilla/Preferences.h"
7
#include "mozilla/Logging.h"
8
#include "nsNSSIOLayer.h"
9
#include "TransportSecurityInfo.h"
10
#include "ssl.h"
11
#include "sslexp.h"
12
13
namespace mozilla {
14
namespace net {
15
16
static bool const kDefaultEnabled = false;
17
Atomic<bool, Relaxed> SSLTokensCache::sEnabled(kDefaultEnabled);
18
19
static uint32_t const kDefaultCapacity = 2048; // 2MB
20
Atomic<uint32_t, Relaxed> SSLTokensCache::sCapacity(kDefaultCapacity);
21
22
static LazyLogModule gSSLTokensCacheLog("SSLTokensCache");
23
#undef LOG
24
#define LOG(args) MOZ_LOG(gSSLTokensCacheLog, mozilla::LogLevel::Debug, args)
25
26
class ExpirationComparator {
27
public:
28
bool Equals(SSLTokensCache::TokenCacheRecord* a,
29
SSLTokensCache::TokenCacheRecord* b) const {
30
return a->mExpirationTime == b->mExpirationTime;
31
}
32
bool LessThan(SSLTokensCache::TokenCacheRecord* a,
33
SSLTokensCache::TokenCacheRecord* b) const {
34
return a->mExpirationTime < b->mExpirationTime;
35
}
36
};
37
38
StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance;
39
StaticMutex SSLTokensCache::sLock;
40
41
uint32_t SSLTokensCache::TokenCacheRecord::Size() const {
42
uint32_t size = mToken.Length() + sizeof(mSessionCacheInfo.mEVStatus) +
43
sizeof(mSessionCacheInfo.mCertificateTransparencyStatus) +
44
mSessionCacheInfo.mServerCertBytes.Length();
45
if (mSessionCacheInfo.mSucceededCertChainBytes) {
46
for (const auto& cert : mSessionCacheInfo.mSucceededCertChainBytes.ref()) {
47
size += cert.Length();
48
}
49
}
50
return size;
51
}
52
53
void SSLTokensCache::TokenCacheRecord::Reset() {
54
mToken.Clear();
55
mExpirationTime = 0;
56
mSessionCacheInfo.mEVStatus = psm::EVStatus::NotEV;
57
mSessionCacheInfo.mCertificateTransparencyStatus =
58
nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
59
mSessionCacheInfo.mServerCertBytes.Clear();
60
mSessionCacheInfo.mSucceededCertChainBytes.reset();
61
}
62
63
NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter)
64
65
// static
66
nsresult SSLTokensCache::Init() {
67
StaticMutexAutoLock lock(sLock);
68
69
if (XRE_GetProcessType() != GeckoProcessType_Default) {
70
return NS_OK;
71
}
72
73
MOZ_ASSERT(!gInstance);
74
75
gInstance = new SSLTokensCache();
76
gInstance->InitPrefs();
77
78
RegisterWeakMemoryReporter(gInstance);
79
80
return NS_OK;
81
}
82
83
// static
84
nsresult SSLTokensCache::Shutdown() {
85
StaticMutexAutoLock lock(sLock);
86
87
if (!gInstance) {
88
return NS_ERROR_UNEXPECTED;
89
}
90
91
UnregisterWeakMemoryReporter(gInstance);
92
93
gInstance = nullptr;
94
95
return NS_OK;
96
}
97
98
SSLTokensCache::SSLTokensCache() : mCacheSize(0) {
99
LOG(("SSLTokensCache::SSLTokensCache"));
100
}
101
102
SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); }
103
104
// static
105
nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
106
uint32_t aTokenLen,
107
nsITransportSecurityInfo* aSecInfo) {
108
StaticMutexAutoLock lock(sLock);
109
110
LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]",
111
PromiseFlatCString(aKey).get(), aTokenLen));
112
113
if (!gInstance) {
114
LOG((" service not initialized"));
115
return NS_ERROR_NOT_INITIALIZED;
116
}
117
118
if (!aSecInfo) {
119
return NS_ERROR_FAILURE;
120
}
121
122
PRUint32 expirationTime;
123
SSLResumptionTokenInfo tokenInfo;
124
if (SSL_GetResumptionTokenInfo(aToken, aTokenLen, &tokenInfo,
125
sizeof(tokenInfo)) != SECSuccess) {
126
LOG((" cannot get expiration time from the token, NSS error %d",
127
PORT_GetError()));
128
return NS_ERROR_FAILURE;
129
}
130
expirationTime = tokenInfo.expirationTime;
131
SSL_DestroyResumptionTokenInfo(&tokenInfo);
132
133
nsCOMPtr<nsIX509Cert> cert;
134
aSecInfo->GetServerCert(getter_AddRefs(cert));
135
if (!cert) {
136
return NS_ERROR_FAILURE;
137
}
138
139
nsTArray<uint8_t> certBytes;
140
nsresult rv = cert->GetRawDER(certBytes);
141
if (NS_FAILED(rv)) {
142
return rv;
143
}
144
145
Maybe<nsTArray<nsTArray<uint8_t>>> succeededCertChainBytes;
146
nsTArray<RefPtr<nsIX509Cert>> succeededCertArray;
147
rv = aSecInfo->GetSucceededCertChain(succeededCertArray);
148
if (NS_FAILED(rv)) {
149
return rv;
150
}
151
152
if (!succeededCertArray.IsEmpty()) {
153
succeededCertChainBytes.emplace();
154
for (const auto& cert : succeededCertArray) {
155
nsTArray<uint8_t> rawCert;
156
nsresult rv = cert->GetRawDER(rawCert);
157
if (NS_FAILED(rv)) {
158
return rv;
159
}
160
succeededCertChainBytes->AppendElement(std::move(rawCert));
161
}
162
}
163
164
bool isEV;
165
rv = aSecInfo->GetIsExtendedValidation(&isEV);
166
if (NS_FAILED(rv)) {
167
return rv;
168
}
169
170
uint16_t certificateTransparencyStatus;
171
rv = aSecInfo->GetCertificateTransparencyStatus(
172
&certificateTransparencyStatus);
173
if (NS_FAILED(rv)) {
174
return rv;
175
}
176
177
TokenCacheRecord* rec = nullptr;
178
179
if (!gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
180
rec = new TokenCacheRecord();
181
rec->mKey = aKey;
182
gInstance->mTokenCacheRecords.Put(aKey, rec);
183
gInstance->mExpirationArray.AppendElement(rec);
184
} else {
185
gInstance->mCacheSize -= rec->Size();
186
rec->Reset();
187
}
188
189
rec->mExpirationTime = expirationTime;
190
MOZ_ASSERT(rec->mToken.IsEmpty());
191
rec->mToken.AppendElements(aToken, aTokenLen);
192
193
rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes);
194
195
rec->mSessionCacheInfo.mSucceededCertChainBytes =
196
std::move(succeededCertChainBytes);
197
198
if (isEV) {
199
rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV;
200
}
201
202
rec->mSessionCacheInfo.mCertificateTransparencyStatus =
203
certificateTransparencyStatus;
204
205
gInstance->mCacheSize += rec->Size();
206
207
gInstance->LogStats();
208
209
gInstance->EvictIfNecessary();
210
211
return NS_OK;
212
}
213
214
// static
215
nsresult SSLTokensCache::Get(const nsACString& aKey,
216
nsTArray<uint8_t>& aToken) {
217
StaticMutexAutoLock lock(sLock);
218
219
LOG(("SSLTokensCache::Get [key=%s]", PromiseFlatCString(aKey).get()));
220
221
if (!gInstance) {
222
LOG((" service not initialized"));
223
return NS_ERROR_NOT_INITIALIZED;
224
}
225
226
TokenCacheRecord* rec = nullptr;
227
228
if (gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
229
if (rec->mToken.Length()) {
230
aToken = rec->mToken;
231
return NS_OK;
232
}
233
}
234
235
LOG((" token not found"));
236
return NS_ERROR_NOT_AVAILABLE;
237
}
238
239
// static
240
bool SSLTokensCache::GetSessionCacheInfo(const nsACString& aKey,
241
SessionCacheInfo& aResult) {
242
StaticMutexAutoLock lock(sLock);
243
244
LOG(("SSLTokensCache::GetSessionCacheInfo [key=%s]",
245
PromiseFlatCString(aKey).get()));
246
247
if (!gInstance) {
248
LOG((" service not initialized"));
249
return false;
250
}
251
252
TokenCacheRecord* rec = nullptr;
253
254
if (gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
255
aResult = rec->mSessionCacheInfo;
256
return true;
257
}
258
259
LOG((" token not found"));
260
return false;
261
}
262
263
// static
264
nsresult SSLTokensCache::Remove(const nsACString& aKey) {
265
StaticMutexAutoLock lock(sLock);
266
267
LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey).get()));
268
269
if (!gInstance) {
270
LOG((" service not initialized"));
271
return NS_ERROR_NOT_INITIALIZED;
272
}
273
274
return gInstance->RemoveLocked(aKey);
275
}
276
277
nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey) {
278
sLock.AssertCurrentThreadOwns();
279
280
LOG(("SSLTokensCache::RemoveLocked [key=%s]",
281
PromiseFlatCString(aKey).get()));
282
283
nsAutoPtr<TokenCacheRecord> rec;
284
285
if (!mTokenCacheRecords.Remove(aKey, &rec)) {
286
LOG((" token not found"));
287
return NS_ERROR_NOT_AVAILABLE;
288
}
289
290
mCacheSize -= rec->Size();
291
292
if (!mExpirationArray.RemoveElement(rec)) {
293
MOZ_ASSERT(false, "token not found in mExpirationArray");
294
}
295
296
LogStats();
297
298
return NS_OK;
299
}
300
301
void SSLTokensCache::InitPrefs() {
302
Preferences::AddAtomicBoolVarCache(
303
&sEnabled, "network.ssl_tokens_cache_enabled", kDefaultEnabled);
304
Preferences::AddAtomicUintVarCache(
305
&sCapacity, "network.ssl_tokens_cache_capacity", kDefaultCapacity);
306
}
307
308
void SSLTokensCache::EvictIfNecessary() {
309
uint32_t capacity = sCapacity << 10; // kilobytes to bytes
310
if (mCacheSize <= capacity) {
311
return;
312
}
313
314
LOG(("SSLTokensCache::EvictIfNecessary - evicting"));
315
316
mExpirationArray.Sort(ExpirationComparator());
317
318
while (mCacheSize > capacity && mExpirationArray.Length() > 0) {
319
if (NS_FAILED(RemoveLocked(mExpirationArray[0]->mKey))) {
320
MOZ_ASSERT(false,
321
"mExpirationArray and mTokenCacheRecords are out of sync!");
322
mExpirationArray.RemoveElementAt(0);
323
}
324
}
325
}
326
327
void SSLTokensCache::LogStats() {
328
LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]",
329
mExpirationArray.Length(), mCacheSize));
330
}
331
332
size_t SSLTokensCache::SizeOfIncludingThis(
333
mozilla::MallocSizeOf mallocSizeOf) const {
334
size_t n = mallocSizeOf(this);
335
336
n += mTokenCacheRecords.ShallowSizeOfExcludingThis(mallocSizeOf);
337
n += mExpirationArray.ShallowSizeOfExcludingThis(mallocSizeOf);
338
339
for (uint32_t i = 0; i < mExpirationArray.Length(); ++i) {
340
n += mallocSizeOf(mExpirationArray[i]);
341
n += mExpirationArray[i]->mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
342
n += mExpirationArray[i]->mToken.ShallowSizeOfExcludingThis(mallocSizeOf);
343
}
344
345
return n;
346
}
347
348
MOZ_DEFINE_MALLOC_SIZE_OF(SSLTokensCacheMallocSizeOf)
349
350
NS_IMETHODIMP
351
SSLTokensCache::CollectReports(nsIHandleReportCallback* aHandleReport,
352
nsISupports* aData, bool aAnonymize) {
353
StaticMutexAutoLock lock(sLock);
354
355
MOZ_COLLECT_REPORT("explicit/network/ssl-tokens-cache", KIND_HEAP,
356
UNITS_BYTES,
357
SizeOfIncludingThis(SSLTokensCacheMallocSizeOf),
358
"Memory used for the SSL tokens cache.");
359
360
return NS_OK;
361
}
362
363
} // namespace net
364
} // namespace mozilla