Wed, 31 Dec 2014 07:16:47 +0100
Revert simplistic fix pending revisit of Mozilla integration attempt.
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/. */
7 #include "CertVerifier.h"
8 #include "OCSPCache.h"
9 #include "nss.h"
10 #include "prerr.h"
11 #include "prprf.h"
12 #include "secerr.h"
14 #include "gtest/gtest.h"
16 const int MaxCacheEntries = 1024;
18 class OCSPCacheTest : public ::testing::Test
19 {
20 protected:
21 static void SetUpTestCase()
22 {
23 NSS_NoDB_Init(nullptr);
24 mozilla::psm::InitCertVerifierLog();
25 }
27 mozilla::psm::OCSPCache cache;
28 };
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 }
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 }
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 }
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 }
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 }
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 }
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));
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 }
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 }
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);
249 // SEC_ERROR_REVOKED_CERTIFICATE overrides everything
250 PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, 50);
251 }
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);
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);
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 }