|
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 Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "CertVerifier.h" |
|
8 #include "OCSPCache.h" |
|
9 #include "nss.h" |
|
10 #include "prerr.h" |
|
11 #include "prprf.h" |
|
12 #include "secerr.h" |
|
13 |
|
14 #include "gtest/gtest.h" |
|
15 |
|
16 const int MaxCacheEntries = 1024; |
|
17 |
|
18 class OCSPCacheTest : public ::testing::Test |
|
19 { |
|
20 protected: |
|
21 static void SetUpTestCase() |
|
22 { |
|
23 NSS_NoDB_Init(nullptr); |
|
24 mozilla::psm::InitCertVerifierLog(); |
|
25 } |
|
26 |
|
27 mozilla::psm::OCSPCache cache; |
|
28 }; |
|
29 |
|
30 // Makes a fake certificate with just the fields we need for testing here. |
|
31 // (And those values are almost entirely bogus.) |
|
32 // stackCert should be stack-allocated memory. |
|
33 static void |
|
34 MakeFakeCert(CERTCertificate* stackCert, const char* subjectValue, |
|
35 const char* issuerValue, const char* serialNumberValue, |
|
36 const char* publicKeyValue) |
|
37 { |
|
38 stackCert->derSubject.data = (unsigned char*)subjectValue; |
|
39 stackCert->derSubject.len = strlen(subjectValue); |
|
40 stackCert->derIssuer.data = (unsigned char*)issuerValue; |
|
41 stackCert->derIssuer.len = strlen(issuerValue); |
|
42 stackCert->serialNumber.data = (unsigned char*)serialNumberValue; |
|
43 stackCert->serialNumber.len = strlen(serialNumberValue); |
|
44 stackCert->derPublicKey.data = (unsigned char*)publicKeyValue; |
|
45 stackCert->derPublicKey.len = strlen(publicKeyValue); |
|
46 CERTName *subject = CERT_AsciiToName(subjectValue); // TODO: this will leak... |
|
47 ASSERT_TRUE(subject); |
|
48 stackCert->subject.arena = subject->arena; |
|
49 stackCert->subject.rdns = subject->rdns; |
|
50 } |
|
51 |
|
52 static void |
|
53 PutAndGet(mozilla::psm::OCSPCache& cache, CERTCertificate* subject, |
|
54 CERTCertificate* issuer, |
|
55 PRErrorCode error, PRTime time) |
|
56 { |
|
57 // The first time is thisUpdate. The second is validUntil. |
|
58 // The caller is expecting the validUntil returned with Get |
|
59 // to be equal to the passed-in time. Since these values will |
|
60 // be different in practice, make thisUpdate less than validUntil. |
|
61 ASSERT_TRUE(time >= 10); |
|
62 SECStatus rv = cache.Put(subject, issuer, error, time - 10, time); |
|
63 ASSERT_TRUE(rv == SECSuccess); |
|
64 PRErrorCode errorOut; |
|
65 PRTime timeOut; |
|
66 ASSERT_TRUE(cache.Get(subject, issuer, errorOut, timeOut)); |
|
67 ASSERT_TRUE(error == errorOut && time == timeOut); |
|
68 } |
|
69 |
|
70 TEST_F(OCSPCacheTest, TestPutAndGet) |
|
71 { |
|
72 SCOPED_TRACE(""); |
|
73 CERTCertificate subject; |
|
74 MakeFakeCert(&subject, "CN=subject1", "CN=issuer1", "001", "key001"); |
|
75 CERTCertificate issuer; |
|
76 MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); |
|
77 PutAndGet(cache, &subject, &issuer, 0, PR_Now()); |
|
78 PRErrorCode errorOut; |
|
79 PRTime timeOut; |
|
80 ASSERT_FALSE(cache.Get(&issuer, &issuer, errorOut, timeOut)); |
|
81 } |
|
82 |
|
83 TEST_F(OCSPCacheTest, TestVariousGets) |
|
84 { |
|
85 SCOPED_TRACE(""); |
|
86 CERTCertificate issuer; |
|
87 MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); |
|
88 PRTime timeIn = PR_Now(); |
|
89 for (int i = 0; i < MaxCacheEntries; i++) { |
|
90 CERTCertificate subject; |
|
91 char subjectBuf[64]; |
|
92 PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i); |
|
93 char serialBuf[8]; |
|
94 PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i); |
|
95 MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000"); |
|
96 PutAndGet(cache, &subject, &issuer, 0, timeIn + i); |
|
97 } |
|
98 CERTCertificate subject; |
|
99 // This will be at the end of the list in the cache |
|
100 PRErrorCode errorOut; |
|
101 PRTime timeOut; |
|
102 MakeFakeCert(&subject, "CN=subject0000", "CN=issuer1", "0000", "key000"); |
|
103 ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); |
|
104 ASSERT_TRUE(errorOut == 0 && timeOut == timeIn); |
|
105 // Once we access it, it goes to the front |
|
106 ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); |
|
107 ASSERT_TRUE(errorOut == 0 && timeOut == timeIn); |
|
108 MakeFakeCert(&subject, "CN=subject0512", "CN=issuer1", "0512", "key000"); |
|
109 // This will be in the middle |
|
110 ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); |
|
111 ASSERT_TRUE(errorOut == 0 && timeOut == timeIn + 512); |
|
112 ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); |
|
113 ASSERT_TRUE(errorOut == 0 && timeOut == timeIn + 512); |
|
114 // We've never seen this certificate |
|
115 MakeFakeCert(&subject, "CN=subject1111", "CN=issuer1", "1111", "key000"); |
|
116 ASSERT_FALSE(cache.Get(&subject, &issuer, errorOut, timeOut)); |
|
117 } |
|
118 |
|
119 TEST_F(OCSPCacheTest, TestEviction) |
|
120 { |
|
121 SCOPED_TRACE(""); |
|
122 CERTCertificate issuer; |
|
123 PRTime timeIn = PR_Now(); |
|
124 MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); |
|
125 // By putting more distinct entries in the cache than it can hold, |
|
126 // we cause the least recently used entry to be evicted. |
|
127 for (int i = 0; i < MaxCacheEntries + 1; i++) { |
|
128 CERTCertificate subject; |
|
129 char subjectBuf[64]; |
|
130 PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i); |
|
131 char serialBuf[8]; |
|
132 PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i); |
|
133 MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000"); |
|
134 PutAndGet(cache, &subject, &issuer, 0, timeIn + i); |
|
135 } |
|
136 CERTCertificate evictedSubject; |
|
137 MakeFakeCert(&evictedSubject, "CN=subject0000", "CN=issuer1", "0000", "key000"); |
|
138 PRErrorCode errorOut; |
|
139 PRTime timeOut; |
|
140 ASSERT_FALSE(cache.Get(&evictedSubject, &issuer, errorOut, timeOut)); |
|
141 } |
|
142 |
|
143 TEST_F(OCSPCacheTest, TestNoEvictionForRevokedResponses) |
|
144 { |
|
145 SCOPED_TRACE(""); |
|
146 CERTCertificate issuer; |
|
147 MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); |
|
148 CERTCertificate notEvictedSubject; |
|
149 MakeFakeCert(¬EvictedSubject, "CN=subject0000", "CN=issuer1", "0000", "key000"); |
|
150 PRTime timeIn = PR_Now(); |
|
151 PutAndGet(cache, ¬EvictedSubject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, timeIn); |
|
152 // By putting more distinct entries in the cache than it can hold, |
|
153 // we cause the least recently used entry that isn't revoked to be evicted. |
|
154 for (int i = 1; i < MaxCacheEntries + 1; i++) { |
|
155 CERTCertificate subject; |
|
156 char subjectBuf[64]; |
|
157 PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i); |
|
158 char serialBuf[8]; |
|
159 PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i); |
|
160 MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000"); |
|
161 PutAndGet(cache, &subject, &issuer, 0, timeIn + i); |
|
162 } |
|
163 PRErrorCode errorOut; |
|
164 PRTime timeOut; |
|
165 ASSERT_TRUE(cache.Get(¬EvictedSubject, &issuer, errorOut, timeOut)); |
|
166 ASSERT_TRUE(errorOut == SEC_ERROR_REVOKED_CERTIFICATE && timeOut == timeIn); |
|
167 CERTCertificate evictedSubject; |
|
168 MakeFakeCert(&evictedSubject, "CN=subject0001", "CN=issuer1", "0001", "key000"); |
|
169 ASSERT_FALSE(cache.Get(&evictedSubject, &issuer, errorOut, timeOut)); |
|
170 } |
|
171 |
|
172 TEST_F(OCSPCacheTest, TestEverythingIsRevoked) |
|
173 { |
|
174 SCOPED_TRACE(""); |
|
175 CERTCertificate issuer; |
|
176 MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); |
|
177 PRTime timeIn = PR_Now(); |
|
178 // Fill up the cache with revoked responses. |
|
179 for (int i = 0; i < MaxCacheEntries; i++) { |
|
180 CERTCertificate subject; |
|
181 char subjectBuf[64]; |
|
182 PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i); |
|
183 char serialBuf[8]; |
|
184 PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i); |
|
185 MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000"); |
|
186 PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, timeIn + i); |
|
187 } |
|
188 CERTCertificate goodSubject; |
|
189 MakeFakeCert(&goodSubject, "CN=subject1025", "CN=issuer1", "1025", "key000"); |
|
190 // This will "succeed", allowing verification to continue. However, |
|
191 // nothing was actually put in the cache. |
|
192 SECStatus result = cache.Put(&goodSubject, &issuer, 0, timeIn + 1025 - 50, |
|
193 timeIn + 1025); |
|
194 ASSERT_TRUE(result == SECSuccess); |
|
195 PRErrorCode errorOut; |
|
196 PRTime timeOut; |
|
197 ASSERT_FALSE(cache.Get(&goodSubject, &issuer, errorOut, timeOut)); |
|
198 |
|
199 CERTCertificate revokedSubject; |
|
200 MakeFakeCert(&revokedSubject, "CN=subject1026", "CN=issuer1", "1026", "key000"); |
|
201 // This will fail, causing verification to fail. |
|
202 result = cache.Put(&revokedSubject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, |
|
203 timeIn + 1026 - 50, timeIn + 1026); |
|
204 PRErrorCode error = PR_GetError(); |
|
205 ASSERT_TRUE(result == SECFailure); |
|
206 ASSERT_TRUE(error == SEC_ERROR_REVOKED_CERTIFICATE); |
|
207 } |
|
208 |
|
209 TEST_F(OCSPCacheTest, VariousIssuers) |
|
210 { |
|
211 SCOPED_TRACE(""); |
|
212 CERTCertificate issuer1; |
|
213 MakeFakeCert(&issuer1, "CN=issuer1", "CN=issuer1", "000", "key000"); |
|
214 CERTCertificate issuer2; |
|
215 MakeFakeCert(&issuer2, "CN=issuer2", "CN=issuer2", "000", "key001"); |
|
216 CERTCertificate issuer3; |
|
217 // Note: same CN as issuer1 |
|
218 MakeFakeCert(&issuer3, "CN=issuer1", "CN=issuer3", "000", "key003"); |
|
219 CERTCertificate subject; |
|
220 MakeFakeCert(&subject, "CN=subject", "CN=issuer1", "001", "key002"); |
|
221 PRTime timeIn = PR_Now(); |
|
222 PutAndGet(cache, &subject, &issuer1, 0, timeIn); |
|
223 PRErrorCode errorOut; |
|
224 PRTime timeOut; |
|
225 ASSERT_TRUE(cache.Get(&subject, &issuer1, errorOut, timeOut)); |
|
226 ASSERT_TRUE(errorOut == 0 && timeOut == timeIn); |
|
227 ASSERT_FALSE(cache.Get(&subject, &issuer2, errorOut, timeOut)); |
|
228 ASSERT_FALSE(cache.Get(&subject, &issuer3, errorOut, timeOut)); |
|
229 } |
|
230 |
|
231 TEST_F(OCSPCacheTest, Times) |
|
232 { |
|
233 SCOPED_TRACE(""); |
|
234 CERTCertificate subject; |
|
235 MakeFakeCert(&subject, "CN=subject1", "CN=issuer1", "001", "key001"); |
|
236 CERTCertificate issuer; |
|
237 MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); |
|
238 PutAndGet(cache, &subject, &issuer, SEC_ERROR_OCSP_UNKNOWN_CERT, 100); |
|
239 PutAndGet(cache, &subject, &issuer, 0, 200); |
|
240 // This should not override the more recent entry. |
|
241 SECStatus rv = cache.Put(&subject, &issuer, SEC_ERROR_OCSP_UNKNOWN_CERT, 100, 100); |
|
242 ASSERT_TRUE(rv == SECSuccess); |
|
243 PRErrorCode errorOut; |
|
244 PRTime timeOut; |
|
245 ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); |
|
246 // Here we see the more recent time. |
|
247 ASSERT_TRUE(errorOut == 0 && timeOut == 200); |
|
248 |
|
249 // SEC_ERROR_REVOKED_CERTIFICATE overrides everything |
|
250 PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, 50); |
|
251 } |
|
252 |
|
253 TEST_F(OCSPCacheTest, NetworkFailure) |
|
254 { |
|
255 SCOPED_TRACE(""); |
|
256 CERTCertificate subject; |
|
257 MakeFakeCert(&subject, "CN=subject1", "CN=issuer1", "001", "key001"); |
|
258 CERTCertificate issuer; |
|
259 MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); |
|
260 PutAndGet(cache, &subject, &issuer, PR_CONNECT_REFUSED_ERROR, 100); |
|
261 PutAndGet(cache, &subject, &issuer, 0, 200); |
|
262 // This should not override the already present entry. |
|
263 SECStatus rv = cache.Put(&subject, &issuer, PR_CONNECT_REFUSED_ERROR, 300, 350); |
|
264 ASSERT_TRUE(rv == SECSuccess); |
|
265 PRErrorCode errorOut; |
|
266 PRTime timeOut; |
|
267 ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); |
|
268 ASSERT_TRUE(errorOut == 0 && timeOut == 200); |
|
269 |
|
270 PutAndGet(cache, &subject, &issuer, SEC_ERROR_OCSP_UNKNOWN_CERT, 400); |
|
271 // This should not override the already present entry. |
|
272 rv = cache.Put(&subject, &issuer, PR_CONNECT_REFUSED_ERROR, 500, 550); |
|
273 ASSERT_TRUE(rv == SECSuccess); |
|
274 ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); |
|
275 ASSERT_TRUE(errorOut == SEC_ERROR_OCSP_UNKNOWN_CERT && timeOut == 400); |
|
276 |
|
277 PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, 600); |
|
278 // This should not override the already present entry. |
|
279 rv = cache.Put(&subject, &issuer, PR_CONNECT_REFUSED_ERROR, 700, 750); |
|
280 ASSERT_TRUE(rv == SECSuccess); |
|
281 ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); |
|
282 ASSERT_TRUE(errorOut == SEC_ERROR_REVOKED_CERTIFICATE && timeOut == 600); |
|
283 } |