OLD | NEW |
| (Empty) |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "net/cert/multi_log_ct_verifier.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/files/file_path.h" | |
10 #include "base/files/file_util.h" | |
11 #include "base/metrics/histogram.h" | |
12 #include "base/metrics/histogram_samples.h" | |
13 #include "base/metrics/statistics_recorder.h" | |
14 #include "base/values.h" | |
15 #include "net/base/capturing_net_log.h" | |
16 #include "net/base/net_errors.h" | |
17 #include "net/base/net_log.h" | |
18 #include "net/base/test_data_directory.h" | |
19 #include "net/cert/ct_log_verifier.h" | |
20 #include "net/cert/ct_serialization.h" | |
21 #include "net/cert/ct_verify_result.h" | |
22 #include "net/cert/pem_tokenizer.h" | |
23 #include "net/cert/sct_status_flags.h" | |
24 #include "net/cert/signed_certificate_timestamp.h" | |
25 #include "net/cert/x509_certificate.h" | |
26 #include "net/test/cert_test_util.h" | |
27 #include "net/test/ct_test_util.h" | |
28 #include "testing/gtest/include/gtest/gtest.h" | |
29 | |
30 namespace net { | |
31 | |
32 namespace { | |
33 | |
34 const char kLogDescription[] = "somelog"; | |
35 const char kSCTCountHistogram[] = | |
36 "Net.CertificateTransparency.SCTsPerConnection"; | |
37 | |
38 class MultiLogCTVerifierTest : public ::testing::Test { | |
39 public: | |
40 void SetUp() override { | |
41 scoped_ptr<CTLogVerifier> log( | |
42 CTLogVerifier::Create(ct::GetTestPublicKey(), kLogDescription)); | |
43 ASSERT_TRUE(log); | |
44 | |
45 verifier_.reset(new MultiLogCTVerifier()); | |
46 verifier_->AddLog(log.Pass()); | |
47 std::string der_test_cert(ct::GetDerEncodedX509Cert()); | |
48 chain_ = X509Certificate::CreateFromBytes( | |
49 der_test_cert.data(), | |
50 der_test_cert.length()); | |
51 ASSERT_TRUE(chain_.get()); | |
52 | |
53 embedded_sct_chain_ = | |
54 CreateCertificateChainFromFile(GetTestCertsDirectory(), | |
55 "ct-test-embedded-cert.pem", | |
56 X509Certificate::FORMAT_AUTO); | |
57 ASSERT_TRUE(embedded_sct_chain_.get()); | |
58 } | |
59 | |
60 bool CheckForSingleVerifiedSCTInResult(const ct::CTVerifyResult& result) { | |
61 return (result.verified_scts.size() == 1U) && | |
62 result.invalid_scts.empty() && | |
63 result.unknown_logs_scts.empty() && | |
64 result.verified_scts[0]->log_description == kLogDescription; | |
65 } | |
66 | |
67 bool CheckForSCTOrigin( | |
68 const ct::CTVerifyResult& result, | |
69 ct::SignedCertificateTimestamp::Origin origin) { | |
70 return (result.verified_scts.size() > 0) && | |
71 (result.verified_scts[0]->origin == origin); | |
72 } | |
73 | |
74 bool CheckForEmbeddedSCTInNetLog(CapturingNetLog& net_log) { | |
75 CapturingNetLog::CapturedEntryList entries; | |
76 net_log.GetEntries(&entries); | |
77 if (entries.size() != 2) | |
78 return false; | |
79 | |
80 const CapturingNetLog::CapturedEntry& received = entries[0]; | |
81 std::string embedded_scts; | |
82 if (!received.GetStringValue("embedded_scts", &embedded_scts)) | |
83 return false; | |
84 if (embedded_scts.empty()) | |
85 return false; | |
86 | |
87 const CapturingNetLog::CapturedEntry& parsed = entries[1]; | |
88 base::ListValue* verified_scts; | |
89 if (!parsed.GetListValue("verified_scts", &verified_scts) || | |
90 verified_scts->GetSize() != 1) { | |
91 return false; | |
92 } | |
93 | |
94 base::DictionaryValue* the_sct; | |
95 if (!verified_scts->GetDictionary(0, &the_sct)) | |
96 return false; | |
97 | |
98 std::string origin; | |
99 if (!the_sct->GetString("origin", &origin)) | |
100 return false; | |
101 if (origin != "embedded_in_certificate") | |
102 return false; | |
103 | |
104 base::ListValue* other_scts; | |
105 if (!parsed.GetListValue("invalid_scts", &other_scts) || | |
106 !other_scts->empty()) { | |
107 return false; | |
108 } | |
109 | |
110 if (!parsed.GetListValue("unknown_logs_scts", &other_scts) || | |
111 !other_scts->empty()) { | |
112 return false; | |
113 } | |
114 | |
115 return true; | |
116 } | |
117 | |
118 std::string GetSCTListWithInvalidSCT() { | |
119 std::string sct(ct::GetTestSignedCertificateTimestamp()); | |
120 | |
121 // Change a byte inside the Log ID part of the SCT so it does | |
122 // not match the log used in the tests | |
123 sct[15] = 't'; | |
124 | |
125 std::string sct_list; | |
126 ct::EncodeSCTListForTesting(sct, &sct_list); | |
127 return sct_list; | |
128 } | |
129 | |
130 bool VerifySinglePrecertificateChain(scoped_refptr<X509Certificate> chain, | |
131 const BoundNetLog& bound_net_log, | |
132 ct::CTVerifyResult* result) { | |
133 return verifier_->Verify(chain.get(), | |
134 std::string(), | |
135 std::string(), | |
136 result, | |
137 bound_net_log) == OK; | |
138 } | |
139 | |
140 bool VerifySinglePrecertificateChain(scoped_refptr<X509Certificate> chain) { | |
141 ct::CTVerifyResult result; | |
142 CapturingNetLog net_log; | |
143 BoundNetLog bound_net_log = | |
144 BoundNetLog::Make(&net_log, NetLog::SOURCE_CONNECT_JOB); | |
145 | |
146 return verifier_->Verify(chain.get(), | |
147 std::string(), | |
148 std::string(), | |
149 &result, | |
150 bound_net_log) == OK; | |
151 } | |
152 | |
153 bool CheckPrecertificateVerification(scoped_refptr<X509Certificate> chain) { | |
154 ct::CTVerifyResult result; | |
155 CapturingNetLog net_log; | |
156 BoundNetLog bound_net_log = | |
157 BoundNetLog::Make(&net_log, NetLog::SOURCE_CONNECT_JOB); | |
158 return (VerifySinglePrecertificateChain(chain, bound_net_log, &result) && | |
159 CheckForSingleVerifiedSCTInResult(result) && | |
160 CheckForSCTOrigin(result, | |
161 ct::SignedCertificateTimestamp::SCT_EMBEDDED) && | |
162 CheckForEmbeddedSCTInNetLog(net_log)); | |
163 } | |
164 | |
165 // Histogram-related helper methods | |
166 int GetValueFromHistogram(std::string histogram_name, int sample_index) { | |
167 base::Histogram* histogram = static_cast<base::Histogram*>( | |
168 base::StatisticsRecorder::FindHistogram(histogram_name)); | |
169 | |
170 if (histogram == NULL) | |
171 return 0; | |
172 | |
173 scoped_ptr<base::HistogramSamples> samples = histogram->SnapshotSamples(); | |
174 return samples->GetCount(sample_index); | |
175 } | |
176 | |
177 int NumConnectionsWithSingleSCT() { | |
178 return GetValueFromHistogram(kSCTCountHistogram, 1); | |
179 } | |
180 | |
181 int NumEmbeddedSCTsInHistogram() { | |
182 return GetValueFromHistogram("Net.CertificateTransparency.SCTOrigin", | |
183 ct::SignedCertificateTimestamp::SCT_EMBEDDED); | |
184 } | |
185 | |
186 int NumValidSCTsInStatusHistogram() { | |
187 return GetValueFromHistogram("Net.CertificateTransparency.SCTStatus", | |
188 ct::SCT_STATUS_OK); | |
189 } | |
190 | |
191 protected: | |
192 scoped_ptr<MultiLogCTVerifier> verifier_; | |
193 scoped_refptr<X509Certificate> chain_; | |
194 scoped_refptr<X509Certificate> embedded_sct_chain_; | |
195 }; | |
196 | |
197 TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCT) { | |
198 ASSERT_TRUE(CheckPrecertificateVerification(embedded_sct_chain_)); | |
199 } | |
200 | |
201 TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithPreCA) { | |
202 scoped_refptr<X509Certificate> chain( | |
203 CreateCertificateChainFromFile(GetTestCertsDirectory(), | |
204 "ct-test-embedded-with-preca-chain.pem", | |
205 X509Certificate::FORMAT_AUTO)); | |
206 ASSERT_TRUE(chain.get()); | |
207 ASSERT_TRUE(CheckPrecertificateVerification(chain)); | |
208 } | |
209 | |
210 TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithIntermediate) { | |
211 scoped_refptr<X509Certificate> chain(CreateCertificateChainFromFile( | |
212 GetTestCertsDirectory(), | |
213 "ct-test-embedded-with-intermediate-chain.pem", | |
214 X509Certificate::FORMAT_AUTO)); | |
215 ASSERT_TRUE(chain.get()); | |
216 ASSERT_TRUE(CheckPrecertificateVerification(chain)); | |
217 } | |
218 | |
219 TEST_F(MultiLogCTVerifierTest, | |
220 VerifiesEmbeddedSCTWithIntermediateAndPreCA) { | |
221 scoped_refptr<X509Certificate> chain(CreateCertificateChainFromFile( | |
222 GetTestCertsDirectory(), | |
223 "ct-test-embedded-with-intermediate-preca-chain.pem", | |
224 X509Certificate::FORMAT_AUTO)); | |
225 ASSERT_TRUE(chain.get()); | |
226 ASSERT_TRUE(CheckPrecertificateVerification(chain)); | |
227 } | |
228 | |
229 TEST_F(MultiLogCTVerifierTest, | |
230 VerifiesSCTOverX509Cert) { | |
231 std::string sct(ct::GetTestSignedCertificateTimestamp()); | |
232 | |
233 std::string sct_list; | |
234 ASSERT_TRUE(ct::EncodeSCTListForTesting(sct, &sct_list)); | |
235 | |
236 ct::CTVerifyResult result; | |
237 EXPECT_EQ(OK, | |
238 verifier_->Verify( | |
239 chain_.get(), std::string(), sct_list, &result, BoundNetLog())); | |
240 ASSERT_TRUE(CheckForSingleVerifiedSCTInResult(result)); | |
241 ASSERT_TRUE(CheckForSCTOrigin( | |
242 result, ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION)); | |
243 } | |
244 | |
245 TEST_F(MultiLogCTVerifierTest, | |
246 IdentifiesSCTFromUnknownLog) { | |
247 std::string sct_list = GetSCTListWithInvalidSCT(); | |
248 ct::CTVerifyResult result; | |
249 | |
250 EXPECT_NE(OK, | |
251 verifier_->Verify( | |
252 chain_.get(), std::string(), sct_list, &result, BoundNetLog())); | |
253 EXPECT_EQ(1U, result.unknown_logs_scts.size()); | |
254 EXPECT_EQ("", result.unknown_logs_scts[0]->log_description); | |
255 } | |
256 | |
257 TEST_F(MultiLogCTVerifierTest, CountsValidSCTsInStatusHistogram) { | |
258 int num_valid_scts = NumValidSCTsInStatusHistogram(); | |
259 | |
260 ASSERT_TRUE(VerifySinglePrecertificateChain(embedded_sct_chain_)); | |
261 | |
262 EXPECT_EQ(num_valid_scts + 1, NumValidSCTsInStatusHistogram()); | |
263 } | |
264 | |
265 TEST_F(MultiLogCTVerifierTest, CountsInvalidSCTsInStatusHistogram) { | |
266 std::string sct_list = GetSCTListWithInvalidSCT(); | |
267 ct::CTVerifyResult result; | |
268 int num_valid_scts = NumValidSCTsInStatusHistogram(); | |
269 int num_invalid_scts = GetValueFromHistogram( | |
270 "Net.CertificateTransparency.SCTStatus", ct::SCT_STATUS_LOG_UNKNOWN); | |
271 | |
272 EXPECT_NE(OK, | |
273 verifier_->Verify( | |
274 chain_.get(), std::string(), sct_list, &result, BoundNetLog())); | |
275 | |
276 ASSERT_EQ(num_valid_scts, NumValidSCTsInStatusHistogram()); | |
277 ASSERT_EQ(num_invalid_scts + 1, | |
278 GetValueFromHistogram("Net.CertificateTransparency.SCTStatus", | |
279 ct::SCT_STATUS_LOG_UNKNOWN)); | |
280 } | |
281 | |
282 TEST_F(MultiLogCTVerifierTest, CountsSingleEmbeddedSCTInConnectionsHistogram) { | |
283 int old_sct_count = NumConnectionsWithSingleSCT(); | |
284 ASSERT_TRUE(CheckPrecertificateVerification(embedded_sct_chain_)); | |
285 EXPECT_EQ(old_sct_count + 1, NumConnectionsWithSingleSCT()); | |
286 } | |
287 | |
288 TEST_F(MultiLogCTVerifierTest, CountsSingleEmbeddedSCTInOriginsHistogram) { | |
289 int old_embedded_count = NumEmbeddedSCTsInHistogram(); | |
290 ASSERT_TRUE(CheckPrecertificateVerification(embedded_sct_chain_)); | |
291 EXPECT_EQ(old_embedded_count + 1, NumEmbeddedSCTsInHistogram()); | |
292 } | |
293 | |
294 TEST_F(MultiLogCTVerifierTest, CountsZeroSCTsCorrectly) { | |
295 int connections_without_scts = GetValueFromHistogram(kSCTCountHistogram, 0); | |
296 EXPECT_FALSE(VerifySinglePrecertificateChain(chain_)); | |
297 ASSERT_EQ(connections_without_scts + 1, | |
298 GetValueFromHistogram(kSCTCountHistogram, 0)); | |
299 } | |
300 | |
301 } // namespace | |
302 | |
303 } // namespace net | |
OLD | NEW |