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 #include "testing/gtest/include/gtest/gtest.h" | |
22 | |
23 namespace certificate_transparency { | |
24 | |
25 namespace { | |
26 | |
27 const char kGetSTHHeaders[] = | |
28 "HTTP/1.1 200 OK\0" | |
29 "Content-Type: application/json; charset=ISO-8859-1\0" | |
30 "\0"; | |
mmenke
2015/08/05 15:37:09
nit: Second \0 at the end not needed (C strings a
Eran Messeri
2015/08/06 09:07:51
Done.
| |
31 | |
32 const char kGetSTHNotFoundHeaders[] = | |
33 "HTTP/1.1 404 Not Found\0" | |
34 "Content-Type: text/html; charset=iso-8859-1\0" | |
35 "\0"; | |
mmenke
2015/08/05 15:37:09
nit: Remove last \0
Eran Messeri
2015/08/06 09:07:51
Done.
| |
36 | |
37 const char kLogSchema[] = "https"; | |
38 const char kLogURL[] = "ct.log.example.com"; | |
39 const char kLogID[] = "some_id"; | |
40 | |
41 class FetchSTHTestJob : public net::URLRequestTestJob { | |
42 public: | |
43 FetchSTHTestJob(const std::string& get_sth_data, | |
44 const std::string& get_sth_headers, | |
45 net::URLRequest* request, | |
46 net::NetworkDelegate* network_delegate) | |
47 : URLRequestTestJob(request, | |
48 network_delegate, | |
49 get_sth_headers, | |
50 get_sth_data, | |
51 true), | |
52 async_io_(false) {} | |
53 | |
54 void set_async_io(bool async_io) { async_io_ = async_io; } | |
55 | |
56 private: | |
57 bool NextReadAsync() override { | |
58 // Response with indication of async IO only once, otherwise the final | |
59 // Read would (incorrectly) be classified as async, causing the | |
60 // URLRequestJob to try reading another time and failing on a CHECK | |
61 // that the raw_read_buffer_ is not null. | |
62 if (async_io_) { | |
63 async_io_ = false; | |
64 return true; | |
65 } | |
66 return false; | |
67 } | |
68 | |
69 ~FetchSTHTestJob() override {} | |
mmenke
2015/08/05 15:37:09
nit: Destructors should go before all other metho
Eran Messeri
2015/08/06 09:07:51
Done.
| |
70 | |
71 bool async_io_; | |
72 | |
73 DISALLOW_COPY_AND_ASSIGN(FetchSTHTestJob); | |
74 }; | |
75 | |
76 class GetSTHResponseHandler : public net::URLRequestInterceptor { | |
77 public: | |
78 GetSTHResponseHandler() | |
79 : async_io_(false), | |
80 response_body_(""), | |
81 response_headers_( | |
82 std::string(kGetSTHHeaders, arraysize(kGetSTHHeaders))) {} | |
83 ~GetSTHResponseHandler() override {} | |
84 | |
85 // URLRequestInterceptor implementation: | |
86 net::URLRequestJob* MaybeInterceptRequest( | |
87 net::URLRequest* request, | |
88 net::NetworkDelegate* network_delegate) const override { | |
89 DVLOG(1) << "MaybeInterceptRequest, headers: " << response_headers_; | |
mmenke
2015/08/05 15:37:09
Should we check that request->url() is the URL we
Eran Messeri
2015/08/06 09:07:51
Done - added an EXPECT_EQ check for the URL.
Note
| |
90 FetchSTHTestJob* job = new FetchSTHTestJob( | |
91 response_body_, response_headers_, request, network_delegate); | |
92 job->set_async_io(async_io_); | |
93 return job; | |
94 } | |
95 | |
96 void set_response_body(std::string response_body) { | |
97 response_body_ = response_body; | |
98 } | |
99 | |
100 void set_response_headers(std::string response_headers) { | |
101 response_headers_ = response_headers; | |
102 } | |
103 | |
104 void set_async_io(bool async_io) { async_io_ = async_io; } | |
105 | |
106 private: | |
107 bool async_io_; | |
108 std::string response_body_; | |
109 std::string response_headers_; | |
110 | |
111 DISALLOW_COPY_AND_ASSIGN(GetSTHResponseHandler); | |
112 }; | |
113 | |
114 class RecordFetchCallbackInvocations { | |
115 public: | |
116 RecordFetchCallbackInvocations(bool expect_success) | |
117 : expect_success_(expect_success), | |
118 invoked_(false), | |
119 net_error_(net::OK), | |
120 http_response_code_(-1) {} | |
121 | |
122 void STHFetched(const std::string& log_id, | |
123 const net::ct::SignedTreeHead& sth) { | |
124 ASSERT_TRUE(expect_success_); | |
125 ASSERT_FALSE(invoked_); | |
126 invoked_ = true; | |
127 // If expected to succeed, expecting the known_good STH. | |
128 net::ct::SignedTreeHead expected_sth; | |
129 net::ct::GetSampleSignedTreeHead(&expected_sth); | |
130 | |
131 ASSERT_EQ(kLogID, log_id); | |
132 ASSERT_EQ(expected_sth.version, sth.version); | |
133 ASSERT_EQ(expected_sth.timestamp, sth.timestamp); | |
134 ASSERT_EQ(expected_sth.tree_size, sth.tree_size); | |
135 ASSERT_STREQ(expected_sth.sha256_root_hash, sth.sha256_root_hash); | |
136 ASSERT_EQ(expected_sth.signature.hash_algorithm, | |
137 sth.signature.hash_algorithm); | |
138 ASSERT_EQ(expected_sth.signature.signature_algorithm, | |
139 sth.signature.signature_algorithm); | |
140 ASSERT_EQ(expected_sth.signature.signature_data, | |
141 sth.signature.signature_data); | |
142 } | |
143 | |
144 void FetchingFailed(const std::string& log_id, | |
145 int net_error, | |
146 int http_response_code) { | |
147 ASSERT_FALSE(expect_success_); | |
148 ASSERT_FALSE(invoked_); | |
149 invoked_ = true; | |
150 net_error_ = net_error; | |
151 http_response_code_ = http_response_code; | |
152 if (net_error_ == net::OK) { | |
153 ASSERT_NE(net::HTTP_OK, http_response_code_); | |
154 } | |
155 } | |
156 | |
157 bool invoked() const { return invoked_; } | |
158 | |
159 int net_error() const { return net_error_; } | |
160 | |
161 int http_response_code() const { return http_response_code_; } | |
162 | |
163 private: | |
164 const bool expect_success_; | |
165 bool invoked_; | |
166 int net_error_; | |
167 int http_response_code_; | |
168 }; | |
169 | |
170 class LogProofFetcherTest : public ::testing::Test { | |
171 public: | |
172 LogProofFetcherTest() | |
173 : log_url_(std::string(kLogSchema) + "://" + std::string(kLogURL) + "/") { | |
174 scoped_ptr<GetSTHResponseHandler> handler(new GetSTHResponseHandler()); | |
175 handler_ = handler.get(); | |
176 | |
177 net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( | |
178 kLogSchema, kLogURL, handler.Pass()); | |
179 | |
180 fetcher_.reset(new LogProofFetcher(&context_)); | |
181 } | |
182 | |
183 ~LogProofFetcherTest() override { | |
184 net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(kLogSchema, | |
185 kLogURL); | |
186 } | |
187 | |
188 protected: | |
189 void SetValidSTHJSONResponse() { | |
190 std::string sth_json_reply_data = net::ct::GetSampleSTHAsJson(); | |
191 handler_->set_response_body(sth_json_reply_data); | |
192 } | |
193 | |
194 void RunFetcherWithCallback(RecordFetchCallbackInvocations* callback) { | |
195 fetcher_->FetchSignedTreeHead( | |
196 log_url_, kLogID, | |
197 base::Bind(&RecordFetchCallbackInvocations::STHFetched, | |
198 base::Unretained(callback)), | |
199 base::Bind(&RecordFetchCallbackInvocations::FetchingFailed, | |
200 base::Unretained(callback))); | |
201 message_loop_.RunUntilIdle(); | |
202 } | |
203 | |
204 base::MessageLoopForIO message_loop_; | |
205 net::TestURLRequestContext context_; | |
206 safe_json::TestingJsonParser::ScopedFactoryOverride factory_override_; | |
207 scoped_ptr<LogProofFetcher> fetcher_; | |
208 const GURL log_url_; | |
209 GetSTHResponseHandler* handler_; | |
210 }; | |
211 | |
212 TEST_F(LogProofFetcherTest, TestValidGetReply) { | |
213 SetValidSTHJSONResponse(); | |
214 | |
215 RecordFetchCallbackInvocations callback(true); | |
216 | |
217 RunFetcherWithCallback(&callback); | |
218 | |
219 ASSERT_TRUE(callback.invoked()); | |
mmenke
2015/08/05 15:37:09
optional: Think these can all be EXPECTs instead,
Eran Messeri
2015/08/06 09:07:51
Done.
| |
220 } | |
221 | |
222 TEST_F(LogProofFetcherTest, TestValidGetReplyAsyncIO) { | |
223 SetValidSTHJSONResponse(); | |
224 handler_->set_async_io(true); | |
225 | |
226 RecordFetchCallbackInvocations callback(true); | |
227 RunFetcherWithCallback(&callback); | |
228 | |
229 ASSERT_TRUE(callback.invoked()); | |
230 } | |
231 | |
232 TEST_F(LogProofFetcherTest, TestInvalidGetReplyIncompleteJSON) { | |
233 std::string sth_json_reply_data = net::ct::CreateSignedTreeHeadJsonString( | |
234 21 /* tree_size */, 123456u /* timestamp */, std::string(), | |
235 std::string()); | |
236 handler_->set_response_body(sth_json_reply_data); | |
237 | |
238 RecordFetchCallbackInvocations callback(false); | |
239 RunFetcherWithCallback(&callback); | |
240 | |
241 ASSERT_TRUE(callback.invoked()); | |
242 ASSERT_EQ(net::ERR_CT_STH_INCOMPLETE, callback.net_error()); | |
243 } | |
244 | |
245 TEST_F(LogProofFetcherTest, TestInvalidGetReplyInvalidJSON) { | |
246 std::string sth_json_reply_data = "{\"tree_size\":21,\"timestamp\":}"; | |
247 handler_->set_response_body(sth_json_reply_data); | |
248 | |
249 RecordFetchCallbackInvocations callback(false); | |
250 RunFetcherWithCallback(&callback); | |
251 | |
252 ASSERT_TRUE(callback.invoked()); | |
253 ASSERT_EQ(net::ERR_CT_STH_PARSING_FAILED, callback.net_error()); | |
254 } | |
255 | |
256 TEST_F(LogProofFetcherTest, TestLogReplyIsTooLong) { | |
257 std::string sth_json_reply_data = net::ct::GetSampleSTHAsJson(); | |
258 // Add kMaxLogResponseSizeInBytes to make sure the response is too big. | |
259 sth_json_reply_data.append( | |
260 std::string(LogProofFetcher::kMaxLogResponseSizeInBytes, ' ')); | |
261 handler_->set_response_body(sth_json_reply_data); | |
262 | |
263 RecordFetchCallbackInvocations callback(false); | |
264 RunFetcherWithCallback(&callback); | |
265 | |
266 ASSERT_TRUE(callback.invoked()); | |
267 ASSERT_EQ(net::ERR_FILE_TOO_BIG, callback.net_error()); | |
268 ASSERT_EQ(net::HTTP_OK, callback.http_response_code()); | |
269 } | |
270 | |
271 TEST_F(LogProofFetcherTest, TestLogReplyIsExactlyMaxSize) { | |
272 std::string sth_json_reply_data = net::ct::GetSampleSTHAsJson(); | |
273 // Extend the reply to be exactly kMaxLogResponseSizeInBytes. | |
274 sth_json_reply_data.append(std::string( | |
275 LogProofFetcher::kMaxLogResponseSizeInBytes - sth_json_reply_data.size(), | |
276 ' ')); | |
277 handler_->set_response_body(sth_json_reply_data); | |
278 | |
279 RecordFetchCallbackInvocations callback(true); | |
280 RunFetcherWithCallback(&callback); | |
281 | |
282 ASSERT_TRUE(callback.invoked()); | |
283 } | |
284 | |
285 TEST_F(LogProofFetcherTest, TestLogRepliesWithHttpError) { | |
286 handler_->set_response_headers( | |
287 std::string(kGetSTHNotFoundHeaders, arraysize(kGetSTHNotFoundHeaders))); | |
288 | |
289 RecordFetchCallbackInvocations callback(false); | |
290 RunFetcherWithCallback(&callback); | |
291 | |
292 ASSERT_TRUE(callback.invoked()); | |
293 ASSERT_EQ(net::OK, callback.net_error()); | |
294 ASSERT_EQ(net::HTTP_NOT_FOUND, callback.http_response_code()); | |
295 } | |
296 | |
297 } // namespace | |
298 | |
299 } // namespace certificate_transparency | |
OLD | NEW |