OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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 "components/certificate_transparency/log_proof_fetcher.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "components/safe_json/testing_json_parser.h" | |
10 #include "net/base/net_errors.h" | |
11 #include "net/base/network_delegate.h" | |
12 #include "net/cert/signed_tree_head.h" | |
13 #include "net/http/http_status_code.h" | |
14 #include "net/test/ct_test_util.h" | |
15 #include "net/url_request/url_request_context.h" | |
16 #include "net/url_request/url_request_filter.h" | |
17 #include "net/url_request/url_request_interceptor.h" | |
18 #include "net/url_request/url_request_job.h" | |
19 #include "net/url_request/url_request_test_job.h" | |
20 #include "net/url_request/url_request_test_util.h" | |
21 | |
22 #include "testing/gtest/include/gtest/gtest.h" | |
mmenke
2015/08/03 18:18:54
This should go up a line
Eran Messeri
2015/08/04 16:15:43
Done.
| |
23 | |
24 namespace certificate_transparency { | |
25 | |
26 namespace { | |
27 | |
28 const char kGetSTHHeaders[] = | |
29 "HTTP/1.1 200 OK\0" | |
30 "Content-Type: application/json; charset=ISO-8859-1\0" | |
31 "\0"; | |
32 | |
33 const char kLogSchema[] = "https"; | |
34 const char kLogURL[] = "ct.log.example.com"; | |
35 | |
36 std::string GetLogID() { | |
37 return std::string("some_id"); | |
38 } | |
mmenke
2015/08/03 18:18:54
Can't this just be a const char[]?
Eran Messeri
2015/08/04 16:15:43
Done.
| |
39 | |
40 class FetchSTHTestJob : public net::URLRequestTestJob { | |
41 public: | |
42 FetchSTHTestJob(const std::string& get_sth_data, | |
43 net::URLRequest* request, | |
44 net::NetworkDelegate* network_delegate) | |
45 : URLRequestTestJob(request, | |
46 network_delegate, | |
47 std::string(kGetSTHHeaders), | |
mmenke
2015/08/03 18:18:54
BUG: This std::string only includes the first lin
Eran Messeri
2015/08/04 16:15:43
Good point, I've passed the size to the std::strin
| |
48 get_sth_data, | |
49 true), | |
50 async_io_(false), | |
51 response_code_(net::HTTP_OK) {} | |
52 | |
53 void SetAsyncIO(bool async_io) { async_io_ = async_io; } | |
54 | |
55 void SetResponseCode(int response_code) { response_code_ = response_code; } | |
56 | |
57 int GetResponseCode() const override { return response_code_; } | |
58 | |
59 protected: | |
60 bool NextReadAsync() override { | |
61 // Response with indication of async IO only once, otherwise the final | |
62 // Read would (incorrectly) be classified as async, causing the | |
63 // URLRequestJob to try reading another time and failing on a CHECK | |
64 // that the raw_read_buffer_ is not null. | |
65 if (async_io_) { | |
66 async_io_ = false; | |
67 return true; | |
68 } | |
69 return false; | |
70 } | |
71 | |
72 private: | |
73 bool async_io_; | |
74 int response_code_; | |
75 | |
76 ~FetchSTHTestJob() override {} | |
77 DISALLOW_COPY_AND_ASSIGN(FetchSTHTestJob); | |
78 }; | |
79 | |
80 class GetSTHResponseHandler : public net::URLRequestInterceptor { | |
81 public: | |
82 GetSTHResponseHandler() | |
83 : async_io_(false), response_data_(""), response_code_(net::HTTP_OK) {} | |
84 ~GetSTHResponseHandler() override {} | |
85 | |
86 // URLRequestInterceptor implementation: | |
87 net::URLRequestJob* MaybeInterceptRequest( | |
88 net::URLRequest* request, | |
89 net::NetworkDelegate* network_delegate) const override { | |
90 FetchSTHTestJob* job = | |
91 new FetchSTHTestJob(response_data_, request, network_delegate); | |
92 job->SetAsyncIO(async_io_); | |
93 job->SetResponseCode(response_code_); | |
94 return job; | |
95 } | |
96 | |
97 void SetResponseData(std::string response_data) { | |
98 response_data_ = response_data; | |
99 } | |
100 | |
101 void SetAsyncIO(bool async_io) { async_io_ = async_io; } | |
102 | |
103 void SetResponseCode(int response_code) { response_code_ = response_code; } | |
104 | |
105 private: | |
106 bool async_io_; | |
107 std::string response_data_; | |
108 int response_code_; | |
109 | |
110 DISALLOW_COPY_AND_ASSIGN(GetSTHResponseHandler); | |
111 }; | |
112 | |
113 class RecordFetchCallbackInvocations { | |
114 public: | |
115 RecordFetchCallbackInvocations() : invoked_(false), failed_(false) {} | |
116 | |
117 virtual void STHFetched(const std::string& log_id, | |
118 const net::ct::SignedTreeHead& sth) { | |
119 invoked_ = true; | |
120 } | |
121 | |
122 void FetchingFailed(const std::string& log_id, | |
123 int net_error, | |
124 int http_response_code) { | |
125 invoked_ = true; | |
126 failed_ = true; | |
127 net_error_ = net_error; | |
128 http_response_code_ = http_response_code; | |
mmenke
2015/08/03 18:18:54
Suggest a sanity check here:
if (net_error_ == ne
Eran Messeri
2015/08/04 16:15:43
Partly done - I've adopted the first clause, as it
| |
129 } | |
130 | |
131 bool invoked() { return invoked_; } | |
132 | |
133 bool failed() { return failed_; } | |
134 | |
135 int net_error() { return net_error_; } | |
136 | |
137 int http_response_code() { return http_response_code_; } | |
138 | |
139 private: | |
140 bool invoked_; | |
141 bool failed_; | |
142 int net_error_; | |
143 int http_response_code_; | |
144 }; | |
145 | |
146 class ExpectedSuccessCallback : public RecordFetchCallbackInvocations { | |
147 public: | |
148 ExpectedSuccessCallback() { net::ct::GetSampleSignedTreeHead(&known_sth_); } | |
149 | |
150 explicit ExpectedSuccessCallback(const net::ct::SignedTreeHead& sth) | |
151 : known_sth_(sth) {} | |
152 | |
153 void STHFetched(const std::string& log_id, | |
154 const net::ct::SignedTreeHead& sth) override { | |
155 ASSERT_EQ(GetLogID(), log_id); | |
156 ASSERT_EQ(sth.version, known_sth_.version); | |
157 ASSERT_EQ(sth.timestamp, known_sth_.timestamp); | |
158 ASSERT_EQ(sth.tree_size, known_sth_.tree_size); | |
159 ASSERT_STREQ(sth.sha256_root_hash, known_sth_.sha256_root_hash); | |
160 ASSERT_EQ(sth.signature.hash_algorithm, | |
161 known_sth_.signature.hash_algorithm); | |
162 ASSERT_EQ(sth.signature.signature_algorithm, | |
163 known_sth_.signature.signature_algorithm); | |
164 ASSERT_EQ(sth.signature.signature_data, | |
165 known_sth_.signature.signature_data); | |
mmenke
2015/08/03 18:18:54
Can we just move this into RecordFetchCallbackInvo
Eran Messeri
2015/08/04 16:15:43
Done - since it's the same expected_sth, I've adde
| |
166 | |
167 RecordFetchCallbackInvocations::STHFetched(log_id, sth); | |
168 } | |
169 | |
170 private: | |
171 net::ct::SignedTreeHead known_sth_; | |
172 }; | |
173 | |
174 class LogProofFetcherTest : public ::testing::Test { | |
175 public: | |
176 LogProofFetcherTest() | |
177 : context_(true), | |
178 log_url_(std::string(kLogSchema) + "://" + std::string(kLogURL) + "/") { | |
179 context_.Init(); | |
mmenke
2015/08/03 18:18:54
Not reason to do this - just remove the Init() cal
Eran Messeri
2015/08/04 16:15:43
Done.
| |
180 } | |
181 | |
182 void SetUp() override { | |
183 scoped_ptr<GetSTHResponseHandler> handler(new GetSTHResponseHandler()); | |
184 handler_ = handler.get(); | |
185 | |
186 net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( | |
187 kLogSchema, kLogURL, handler.Pass()); | |
188 | |
189 fetcher_.reset(new LogProofFetcher(&context_)); | |
mmenke
2015/08/03 18:18:54
Can just do this in the constructor (And TearDown
Eran Messeri
2015/08/04 16:15:43
Done.
| |
190 } | |
191 | |
192 void TearDown() override { | |
193 net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(kLogSchema, | |
194 kLogURL); | |
195 } | |
196 | |
197 protected: | |
198 void SetValidSTHJSONResponse() { | |
199 std::string sth_json_reply_data = net::ct::GetSampleSTHAsJson(); | |
200 handler_->SetResponseData(sth_json_reply_data); | |
201 } | |
202 | |
203 void RunFetcherWithCallback(RecordFetchCallbackInvocations* callback) { | |
204 fetcher_->FetchSignedTreeHead( | |
205 log_url_, GetLogID(), | |
206 base::Bind(&RecordFetchCallbackInvocations::STHFetched, | |
207 base::Unretained(callback)), | |
208 base::Bind(&RecordFetchCallbackInvocations::FetchingFailed, | |
209 base::Unretained(callback))); | |
210 message_loop_.RunUntilIdle(); | |
mmenke
2015/08/03 18:18:54
Not a big fan of RunUntilIdle - it does generally
Eran Messeri
2015/08/04 16:15:43
Not entirely sure why, but the interceptor (added
mmenke
2015/08/04 19:54:29
Not that I'm aware of. You need a MessageLoopForI
| |
211 } | |
212 | |
213 base::MessageLoopForIO message_loop_; | |
214 net::TestURLRequestContext context_; | |
215 safe_json::TestingJsonParser::ScopedFactoryOverride factory_override_; | |
216 scoped_ptr<LogProofFetcher> fetcher_; | |
217 GURL log_url_; | |
mmenke
2015/08/03 18:18:54
const
Eran Messeri
2015/08/04 16:15:43
Done.
| |
218 GetSTHResponseHandler* handler_; | |
219 }; | |
220 | |
221 TEST_F(LogProofFetcherTest, TestValidGetSTHReply) { | |
mmenke
2015/08/03 18:18:54
Suggest getting rid of STH in test names, to make
Eran Messeri
2015/08/04 16:15:43
Done - removed STH from test names.
| |
222 SetValidSTHJSONResponse(); | |
223 | |
224 ExpectedSuccessCallback cb; | |
mmenke
2015/08/03 18:18:54
optional: Suggest just writing out callback. Sty
Eran Messeri
2015/08/04 16:15:43
Done.
| |
225 RunFetcherWithCallback(&cb); | |
226 | |
227 ASSERT_TRUE(cb.invoked()); | |
228 ASSERT_FALSE(cb.failed()); | |
229 } | |
230 | |
231 TEST_F(LogProofFetcherTest, TestValidGetSTHReplyAsyncIO) { | |
232 SetValidSTHJSONResponse(); | |
233 handler_->SetAsyncIO(true); | |
234 | |
235 ExpectedSuccessCallback cb; | |
236 RunFetcherWithCallback(&cb); | |
237 | |
238 ASSERT_TRUE(cb.invoked()); | |
239 ASSERT_FALSE(cb.failed()); | |
240 } | |
241 | |
242 TEST_F(LogProofFetcherTest, TestInvalidGetSTHReplyIncompleteSTH) { | |
243 std::string sth_json_reply_data = net::ct::CreateSignedTreeHeadJsonString( | |
244 21 /* tree_size */, 123456u /* timestamp */, std::string(""), | |
245 std::string("")); | |
246 handler_->SetResponseData(sth_json_reply_data); | |
247 | |
248 RecordFetchCallbackInvocations cb; | |
249 RunFetcherWithCallback(&cb); | |
250 | |
251 ASSERT_TRUE(cb.invoked()); | |
252 ASSERT_TRUE(cb.failed()); | |
253 ASSERT_EQ(net::ERR_CT_STH_INCOMPLETE, cb.net_error()); | |
254 } | |
255 | |
256 TEST_F(LogProofFetcherTest, TestInvalidGetSTHReplyInvalidJSON) { | |
257 std::string sth_json_reply_data = "{\"tree_size\":21,\"timestamp\":}"; | |
258 handler_->SetResponseData(sth_json_reply_data); | |
259 | |
260 RecordFetchCallbackInvocations cb; | |
261 RunFetcherWithCallback(&cb); | |
262 | |
263 ASSERT_TRUE(cb.invoked()); | |
264 ASSERT_TRUE(cb.failed()); | |
265 ASSERT_EQ(net::ERR_CT_STH_PARSING_FAILED, cb.net_error()); | |
266 } | |
267 | |
268 TEST_F(LogProofFetcherTest, TestLogReplyIsTooLong) { | |
269 std::string sth_json_reply_data = net::ct::GetSampleSTHAsJson(); | |
270 // kMaxLogResponseSizeInBytes is 600 - add that much to make sure the response | |
271 // is too big. | |
272 sth_json_reply_data.append(std::string(600, ' ')); | |
mmenke
2015/08/03 18:18:54
It's much better to expose MaxLogResponseSizeInByt
Eran Messeri
2015/08/04 16:15:43
Done.
| |
273 handler_->SetResponseData(sth_json_reply_data); | |
274 | |
275 RecordFetchCallbackInvocations cb; | |
276 RunFetcherWithCallback(&cb); | |
277 | |
278 ASSERT_TRUE(cb.invoked()); | |
279 ASSERT_TRUE(cb.failed()); | |
280 ASSERT_EQ(net::ERR_FILE_TOO_BIG, cb.net_error()); | |
281 ASSERT_EQ(net::HTTP_OK, cb.http_response_code()); | |
282 } | |
283 | |
284 TEST_F(LogProofFetcherTest, TestLogReplyIsExactlyMaxSize) { | |
285 std::string sth_json_reply_data = net::ct::GetSampleSTHAsJson(); | |
286 // Extend the reply to be exactly kMaxLogResponseSizeInBytes. | |
287 sth_json_reply_data.append( | |
288 std::string(600 - sth_json_reply_data.size(), ' ')); | |
289 handler_->SetResponseData(sth_json_reply_data); | |
290 | |
291 ExpectedSuccessCallback cb; | |
292 RunFetcherWithCallback(&cb); | |
293 | |
294 ASSERT_TRUE(cb.invoked()); | |
295 ASSERT_FALSE(cb.failed()); | |
296 } | |
297 | |
298 TEST_F(LogProofFetcherTest, TestLogRepliesWithHttpError) { | |
299 handler_->SetResponseCode(net::HTTP_NOT_FOUND); | |
300 | |
301 RecordFetchCallbackInvocations cb; | |
302 RunFetcherWithCallback(&cb); | |
303 | |
304 ASSERT_TRUE(cb.invoked()); | |
305 ASSERT_TRUE(cb.failed()); | |
306 ASSERT_EQ(net::OK, cb.net_error()); | |
307 ASSERT_EQ(net::HTTP_NOT_FOUND, cb.http_response_code()); | |
308 } | |
309 | |
310 } // namespace | |
311 | |
312 } // namespace certificate_transparency | |
OLD | NEW |