Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(115)

Side by Side Diff: components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc

Issue 338483002: Chrome Participated Tamper Detect (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698