OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 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 // This file implements the tamper detection logic, where we want to detect | |
6 // whether there are middleboxes and whether they are tampering the response | |
7 // which maybe break correct communication between Chrome and data reduction | |
bengr
2014/07/02 17:30:59
and the data...
xingx
2014/07/06 03:18:18
Not sure if I get it right, change to "break corre
| |
8 // proxy. | |
9 // | |
10 // A high-level description of our tamper detection process works in two steps: | |
11 // 1. Data reduction proxy selects the requests we want to detect tamper; | |
12 // for the selected ones, data reduction proxy generates a series of | |
13 // fingerprints of the response, and append it to the Chrome-Proxy header; | |
14 // 2. At Chrome client side, once it sees such fingerprints, it uses the | |
15 // same method of data reduction proxy to generate the fingerprints on | |
16 // the response it receives, compare it to the result on the response | |
17 // data reduction proxy sends, i.e., the attached fingerprints in | |
18 // Chrome-Proxy header, to see if they are identical and report if there is | |
19 // any tamper detected to UMA. | |
20 // | |
21 // Right now we have 4 fingerprints. Chrome parses the fingerprints, check | |
22 // whether there is tampering on each of them, and report the result to UMA: | |
23 // 1. Chrome-Proxy header | |
24 // whether values of Chrome-Proxy have been tampered; | |
25 // 2. Via header | |
26 // whether there are middleboxes between Chrome and data reduction proxy; | |
27 // 3. Some other headers | |
28 // whether the values of a list of headers have been tampered; | |
29 // 4. Content-Length header | |
30 // whether the value of Content-Length is different to what data reduction | |
31 // proxy sends, which indicates that the response body has been tampered. | |
32 // | |
33 // Then Chrome reports tamper or not information to UMA. | |
34 // In general, Chrome reports the number of tampers for each fingerprint | |
35 // on different carriers, as well as total number of tamper detection handled. | |
36 // The only special case is the 4th one, Content-Length, | |
37 // which we have another dimension, MIME types, Chrome reports the tamper on | |
38 // different MIME type independently. | |
39 | |
40 | |
41 #include <string.h> | |
42 #include <algorithm> | |
43 #include <vector> | |
44 | |
45 #include "base/base64.h" | |
46 #include "base/md5.h" | |
47 #include "base/metrics/sparse_histogram.h" | |
48 #include "base/strings/string_number_conversions.h" | |
49 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_de tect.h" | |
50 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h" | |
51 | |
52 #include "net/android/network_library.h" | |
53 #include "net/http/http_request_headers.h" | |
54 #include "net/http/http_util.h" | |
55 | |
56 // Utility function... | |
57 // Sort the strings in |values| alphabetically, concatenate them into a string. | |
58 std::string ValuesToSortedString(std::vector<std::string> &values) { | |
59 std::string aggregated_values; | |
60 | |
61 std::sort(values.begin(), values.end()); | |
62 for (size_t i = 0; i < values.size(); ++i) | |
63 aggregated_values += values[i] + ","; | |
64 return aggregated_values; | |
65 } | |
66 | |
67 namespace data_reduction_proxy { | |
68 | |
69 // Utility function... | |
70 // For a given string, calculate and return the MD5 hash value of the string. | |
71 std::string GetMD5(const std::string &input) { | |
bengr
2014/07/02 17:30:59
Look at https://code.google.com/p/chromium/codesea
xingx
2014/07/06 03:18:18
We can discuss about this, right now I'm using bas
| |
72 base::MD5Context context; | |
73 base::MD5Init(&context); | |
74 base::MD5Update(&context, input); | |
75 base::MD5Digest new_digest; | |
76 base::MD5Final(&new_digest, &context); | |
77 return std::string((char*)new_digest.a, ARRAYSIZE_UNSAFE(new_digest.a)); | |
78 } | |
79 | |
80 // Utility function... | |
81 // For a given |header_name|, get all its values and return the vector contains | |
82 // all of the values. | |
83 std::vector<std::string> GetHeaderValues( | |
84 const net::HttpResponseHeaders* headers, const std::string& header_name) { | |
85 std::vector<std::string> values; | |
86 std::string value; | |
87 void* iter = NULL; | |
88 while (headers->EnumerateHeader(&iter, header_name, &value)) { | |
89 values.push_back(value); | |
90 } | |
91 return values; | |
92 } | |
93 | |
94 // Utility function, exposed for unittest. | |
95 // For Chrome-Proxy header values |values|, check whether it contains two | |
96 // fingerprints: | |
97 // |kTamperDetectFingerprintChromeProxy| and |kTamperDetectFingerprint|. | |
98 // If not, means that there is no tamper detect request, return false; | |
99 // otherwise save these two fingerprints to: | |
100 // |chrome_proxy_fingerprint| and |other_fingerprints| | |
101 // for later use and return true. | |
102 bool ContainsTamperDetectFingerprints(std::vector<std::string>& values, | |
bolian
2014/07/02 23:47:37
Don't repeat func doc from the .h file. For expos
xingx
2014/07/06 03:18:18
Done.
| |
103 std::string& chrome_proxy_fingerprint, | |
104 std::string& other_fingerprints) { | |
105 // Enumerate the values of Chrome-Proxy header and check if there is the | |
106 // fingerprint of Chrome-Proxy header (kTamperDetectFingerprintChromeProxy) | |
107 // and other fingerprints (kTamperDetectFingerprint) | |
108 bool contains_tamper_detect_fingerprints = false; | |
109 for (size_t i = 0; i < values.size(); ++i) { | |
110 if (values[i].find(kTamperDetectFingerprintChromeProxy) == 0) { | |
111 contains_tamper_detect_fingerprints = true; | |
112 // Save Chrome-Proxy fingerprint. | |
113 chrome_proxy_fingerprint = values[i]. | |
114 substr(strlen(kTamperDetectFingerprintChromeProxy)); | |
115 // Erase Chrome-Proxy's fingerprint from Chrome-Proxy header for | |
116 // later fingerprint calculation. | |
117 values.erase(values.begin() + (i--)); | |
bengr
2014/07/02 17:30:59
Separate out the decrement and explain why it is n
xingx
2014/07/06 03:18:18
Done.
| |
118 } | |
119 else if (values[i].find(kTamperDetectFingerprint) == 0) | |
bengr
2014/07/02 17:30:59
Add curly braces.
xingx
2014/07/06 03:18:18
Done.
| |
120 // Save other fingerprints. | |
121 other_fingerprints = values[i].substr(strlen(kTamperDetectFingerprint)); | |
122 } | |
123 return contains_tamper_detect_fingerprints; | |
124 } | |
125 | |
126 // The main function for detecting tamper. | |
127 // For such response, the function checks whether there is a tamper detect | |
128 // request from data reduction proxy. | |
129 // if so, it checks whether there are tampers for each fingerprint one by one | |
130 // and report the results to UMA. | |
131 void CheckResponseFingerprint(const net::HttpResponseHeaders* headers, | |
132 const bool is_secure_scheme) | |
133 { | |
134 // Get all the values of Chrome-Proxy header. | |
bolian
2014/07/02 23:47:37
rm doc here. This is obvious from reading the code
xingx
2014/07/06 03:18:18
Done.
| |
135 std::vector<std::string> values = GetHeaderValues(headers, "Chrome-Proxy"); | |
136 | |
137 // |chrome_proxy_fingerprint| holds the value of fingerprint of | |
138 // Chrome-Proxy header. | |
139 // |other_fingerprints| holds the value of other fingerprints. | |
140 std::string chrome_proxy_fingerprint, other_fingerprints; | |
141 | |
142 // Check if there are fingerprints (and thus need to detect tamper). | |
143 if (!ContainsTamperDetectFingerprints(values, | |
144 chrome_proxy_fingerprint, | |
145 other_fingerprints)) | |
146 return; | |
147 | |
148 // Found tamper detect request field. | |
149 // Get carrier ID. | |
150 unsigned mcc_mnc = 0; | |
151 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &mcc_mnc); | |
152 | |
153 // Initialize tamper detect object. | |
bolian
2014/07/02 23:47:37
rm. Useless comment.
xingx
2014/07/06 03:18:18
Done.
| |
154 DataReductionProxyTamperDetect tamper_detect(headers, is_secure_scheme, | |
155 mcc_mnc, &values); | |
156 | |
157 // Check if Chrome-Proxy header has been tampered. | |
158 if (tamper_detect.CheckHeaderChromeProxy(chrome_proxy_fingerprint)) { | |
159 UMA_REPORT(is_secure_scheme, | |
160 "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy", | |
161 "DataReductionProxy.HTTPHeaderTampered_ChromeProxy", | |
162 mcc_mnc); | |
163 return; | |
164 } else | |
165 UMA_REPORT(is_secure_scheme, | |
166 "DataReductionProxy.HTTPSHeaderTamperDetection", | |
167 "DataReductionProxy.HTTPHeaderTamperDetection", | |
168 mcc_mnc); | |
169 | |
170 // Separate fingerprints from |other_fingerprints|. | |
171 net::HttpUtil::ValuesIterator it(other_fingerprints.begin(), | |
172 other_fingerprints.end(), '|'); | |
173 | |
174 // For each fingerprint, get its name |key| and the fingerprint value |value| | |
175 // from data reduction proxy. CheckReportFingerprint will handle the tamper | |
176 // detect and corresponding UMA report. | |
177 size_t delimiter_pos = std::string::npos; | |
178 while (it.GetNext()) { | |
179 delimiter_pos = it.value().find("="); | |
180 if (delimiter_pos == std::string::npos) | |
181 continue; | |
182 std::string key = it.value().substr(0, delimiter_pos); | |
183 std::string value = it.value().substr(delimiter_pos + 1); | |
184 tamper_detect.CheckReportFingerprint(key, value); | |
185 } | |
186 return; | |
187 } | |
188 | |
189 // Constructor of DataReductionProxyTamperDetect class. | |
bolian
2014/07/02 23:47:37
delete "// Constructor of DataReductionProxyTamper
xingx
2014/07/06 03:18:18
Done.
| |
190 // It initialize the function pointer map. | |
191 // Right now we have 3 fingerprints to check (besides Chrome-Proxy header's | |
192 // fingerprint, which has been handled specially. | |
193 // In the future we can add more fingerprints to check, need to implement | |
194 // a pair of functions: | |
195 // checking and reporting, and then add it to the function map. | |
196 DataReductionProxyTamperDetect::DataReductionProxyTamperDetect( | |
197 const net::HttpResponseHeaders* headers, const bool secure, | |
198 const unsigned mcc_mnc_, std::vector<std::string>* values) | |
199 : response_headers(headers), | |
200 is_secure_scheme(secure), | |
201 mcc_mnc(mcc_mnc_), | |
202 clean_chrome_proxy_header_values(values) { | |
203 check_report_func_map = std::map<std::string, CheckReportFuncs>(); | |
204 | |
205 check_report_func_map[kTamperDetectFingerprintVia] = | |
206 {&DataReductionProxyTamperDetect::CheckHeaderVia, | |
207 &DataReductionProxyTamperDetect::ReportHeaderVia}; | |
208 | |
209 check_report_func_map[kTamperDetectFingerprintOther] = | |
210 {&DataReductionProxyTamperDetect::CheckHeaderOtherHeaders, | |
211 &DataReductionProxyTamperDetect::ReportHeaderOtherHeaders}; | |
212 | |
213 check_report_func_map[kTamperDetectFingerprintContengLength] = | |
214 {&DataReductionProxyTamperDetect::CheckHeaderContentLength, | |
215 &DataReductionProxyTamperDetect::ReportHeaderContentLength}; | |
216 }; | |
217 | |
218 DataReductionProxyTamperDetect::~DataReductionProxyTamperDetect() {}; | |
219 | |
220 // For fingerprint name tag |key|, call it's corresponding checking function | |
221 // and reporting function. | |
222 void DataReductionProxyTamperDetect::CheckReportFingerprint( | |
223 const std::string& key, const std::string& fingerprint) { | |
224 CheckReportFuncs funcs = check_report_func_map[key]; | |
225 if ((this->*funcs.check_tamper_func)(fingerprint)) | |
bengr
2014/07/02 17:30:59
use base::Callback
xingx
2014/07/06 03:18:18
Changed to switch / enum.
| |
226 (this->*funcs.report_tamper_func)(); | |
227 } | |
228 | |
229 // Check whether Chrome-Proxy header has been tampered. | |
230 // |fingerprint| is the fingerprint Chrome received from data reduction proxy, | |
231 // which is Base64 encoded. Decode it first. Calculate the hash value of | |
232 // Chrome-Proxy header. Note that |clean_chrome_proxy_header_values| holds | |
233 // the values of Chrome-Proxy header with its own fingerprint removed, | |
234 // so it's the correct values to be used to calculate fingerprint. | |
235 // Compare calculated fingerprint to the fingerprint from data reduction proxy | |
236 // (the removed value) and see there is tamper detected. | |
237 bool DataReductionProxyTamperDetect::CheckHeaderChromeProxy( | |
238 const std::string& fingerprint) { | |
239 std::string received_fingerprint; | |
240 if (!base::Base64Decode(fingerprint, &received_fingerprint)) | |
241 return false; | |
242 | |
243 // Calculate the MD5 hash value of Chrome-Proxy. | |
244 std::string actual_fingerprint = GetMD5( | |
245 ValuesToSortedString(*clean_chrome_proxy_header_values)); | |
246 | |
247 // Compare and check if there is tamper detected. | |
248 return received_fingerprint != actual_fingerprint; | |
249 } | |
250 | |
251 // For Via header tamper detection... | |
252 // Check whether there are proxies/middleboxes between Chrome | |
253 // and data reduction proxy. Concretely, it checks whether there are other | |
254 // proxies/middleboxes' name after data reduction proxy's name in Via header. | |
255 bool DataReductionProxyTamperDetect::CheckHeaderVia( | |
256 const std::string& fingerprint) { | |
257 | |
258 std::vector<std::string> vias = GetHeaderValues(response_headers, "via"); | |
259 | |
260 // If there is no tag, then data reduction proxy's tag have been removed. | |
261 if (vias.size() == 0) return true; | |
262 // Check whether the last proxy/middlebox is data reduction proxy or not. | |
263 return vias[vias.size() - 1]. | |
264 find(kDataReductionProxyViaValue) == std::string::npos; | |
265 } | |
266 | |
267 // Report Via header tamper detected. | |
268 void DataReductionProxyTamperDetect::ReportHeaderVia() { | |
269 UMA_REPORT(is_secure_scheme, | |
270 "DataReductionProxy.HTTPSHeaderTampered_Via", | |
271 "DataReductionProxy.HTTPHeaderTampered_Via", | |
272 mcc_mnc); | |
273 } | |
274 | |
275 | |
276 // For other headers tamper detection... | |
277 // Check whether values of a predefined list of headers have been tampered. | |
278 // The format for |fingerprint| is: | |
279 // [base64fingerprint]:header_name1:header_namer2:... | |
280 // Firstly extract the header names in the |fingerprint|. | |
281 // For each header, | |
282 // 1) we get all the values of such header; | |
283 // 2) we sort the values alphabetically; | |
284 // 3) we concatenate sorted values to a string and calculate MD5 hash on it. | |
285 // Finally, we compare whether it equals to the fingerprint from | |
286 // data reduction proxy. | |
287 bool DataReductionProxyTamperDetect::CheckHeaderOtherHeaders( | |
288 const std::string& fingerprint) { | |
bengr
2014/07/02 17:30:59
indent 4.
xingx
2014/07/06 03:18:18
Done.
| |
289 std::string received_fingerprint; | |
290 | |
291 net::HttpUtil::ValuesIterator it(fingerprint.begin(), | |
292 fingerprint.end(), ':'); | |
293 | |
294 // Make sure there is [base64fingerprint] and it can be decoded. | |
295 if (!(it.GetNext() && | |
296 base::Base64Decode(std::string(it.value()), &received_fingerprint))) | |
297 return false; | |
298 | |
299 std::string header_values; | |
300 // Enumerate the list of headers. | |
301 while (it.GetNext()) { | |
302 // Get values of one header. | |
303 std::vector<std::string> values = GetHeaderValues(response_headers, | |
bengr
2014/07/02 17:30:59
move GetHeaderValues to next line and indent 4.
xingx
2014/07/06 03:18:18
Done.
| |
304 std::string(it.value())); | |
305 // Sort the values and concatenate them. | |
306 header_values += ValuesToSortedString(values) + ";"; | |
307 } | |
308 | |
309 // Calculate MD5 hash value on the concatenated string. | |
310 std::string actual_fingerprint = GetMD5(header_values); | |
311 | |
312 return received_fingerprint != actual_fingerprint; | |
313 } | |
314 | |
315 // Report other headers tamper detected. | |
316 void DataReductionProxyTamperDetect::ReportHeaderOtherHeaders() { | |
317 UMA_REPORT(is_secure_scheme, | |
318 "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders", | |
319 "DataReductionProxy.HTTPHeaderTampered_OtherHeaders", | |
320 mcc_mnc); | |
321 } | |
322 | |
323 | |
324 // For Content-Length tamper detection... | |
325 // Check whether the Content-Length value is different from what | |
326 // data reduction proxy sees. This is an indicator that the response body | |
327 // have been modified. | |
328 // It's modified only if we can decode Content-Length numbers at both end | |
329 // and such two numbers are not equal. | |
330 bool DataReductionProxyTamperDetect::CheckHeaderContentLength( | |
331 const std::string& fingerprint) { | |
332 int received_content_length, actual_content_length; | |
333 // If Content-Length value from data reduction proxy is not available or | |
334 // it cannot be converted to integer, pass. | |
335 if (base::StringToInt(fingerprint, &received_content_length)) { | |
336 std::string actual_content_length_; | |
337 // If there is Content-Length at Chrome client side is not available, pass. | |
338 if (response_headers->GetNormalizedHeader("Content-Length", | |
339 &actual_content_length_)) { | |
340 // If the Content-Length value cannot be converted to integer, | |
341 // i.e., not valid, pass. | |
342 if (!base::StringToInt(actual_content_length_, &actual_content_length)) | |
343 return false; | |
344 return received_content_length != actual_content_length; | |
345 } | |
346 } | |
347 else | |
348 { | |
349 LOG(WARNING) << "xing can't convert"; | |
bengr
2014/07/02 17:30:59
remove.
xingx
2014/07/06 03:18:18
Done.
| |
350 } | |
351 return false; | |
352 } | |
353 | |
354 // Report Content-Length tamper detected. | |
355 // Get MIME type of the response and report to different UMA histogram. | |
356 // Right now MIME types contain JavaScript, CSS, Images, and others. | |
357 void DataReductionProxyTamperDetect::ReportHeaderContentLength() { | |
358 std::string mime_type; | |
359 // Get MIME type. | |
360 response_headers->GetMimeType(&mime_type); | |
361 UMA_REPORT(is_secure_scheme, | |
362 "DataReductionProxy.HTTPSHeaderTampered_ContentLength", | |
363 "DataReductionProxy.HTTPHeaderTampered_ContentLength", | |
364 mcc_mnc); | |
365 | |
366 // Report tampered JavaScript. | |
367 if (mime_type.compare("text/javascript") == 0 || | |
368 mime_type.compare("application/x-javascript") == 0 || | |
369 mime_type.compare("application/javascript") == 0) | |
370 UMA_REPORT(is_secure_scheme, | |
371 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS", | |
372 "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS", | |
373 mcc_mnc); | |
374 // Report tampered CSSs. | |
375 else if (mime_type.compare("text/css") == 0) | |
376 UMA_REPORT(is_secure_scheme, | |
377 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS", | |
378 "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS", | |
379 mcc_mnc); | |
380 // Report tampered images. | |
381 else if (mime_type.find("image") == 0) | |
382 UMA_REPORT(is_secure_scheme, | |
383 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image", | |
384 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image", | |
385 mcc_mnc); | |
386 // Report tampered other MIME types. | |
387 else | |
388 UMA_REPORT(is_secure_scheme, | |
389 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other", | |
390 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other", | |
391 mcc_mnc); | |
392 } | |
393 | |
394 | |
bengr
2014/07/02 17:30:59
remove blank line.
xingx
2014/07/06 03:18:18
Done.
| |
395 } // namespace data_reduction_proxy | |
OLD | NEW |