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

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: addressed most of comments 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 and data transfer between Chrome
8 // and data reduction 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 them 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/histogram.h"
48 #include "base/metrics/sparse_histogram.h"
49 #include "base/strings/string_number_conversions.h"
50 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_de tect.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 // Macro for UMA report.
57 // If |scheme_is_https| is true, report to |https_histogram|,
58 // otherwise report to |http_histogram|.
59 // Both's bucket are Carrier IDs |mcc_mnc|.
60 // The other histogram counts the total number, |http(s)_histogram| "_Total".
61 // which only has one bucket, 0.
62 #define UMA_REPORT(scheme_is_https, http_histogram, https_histogram, mcc_mnc) \
63 do { \
64 if (scheme_is_https) { \
65 UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, mcc_mnc); \
66 UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
67 } else { \
68 UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, mcc_mnc); \
69 UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
70 }\
71 } while (0)
72
73
74
75
76
77 namespace data_reduction_proxy {
78
79 const char kTamperDetectFingerprint[] = "fp=";
80 const char kTamperDetectFingerprintChromeProxy[] = "cp=";
81
82 // Fingerprint |kTamperDetectFingerprint| contains multiple
83 // fingerprints, each starts with a tag followed by "=" and its fingerprint
84 // value. Three fingerprints and their respective tags are defined below.
85 const char kTamperDetectFingerprintVia[] = "via";
86 const char kTamperDetectFingerprintOther[] = "oh";
87 const char kTamperDetectFingerprintContengLength[] = "cl";
88
89 // Utility function, exposed for unittest.
90 // Enumerate the values of Chrome-Proxy header and check if there is the
91 // fingerprint of Chrome-Proxy header (kTamperDetectFingerprintChromeProxy)
92 // and other fingerprints (kTamperDetectFingerprint)
93 bool ContainsTamperDetectFingerprints(std::vector<std::string>& values,
94 std::string& chrome_proxy_fingerprint,
95 std::string& other_fingerprints) {
96 bool contains_tamper_detect_fingerprints = false;
97 for (size_t i = 0; i < values.size(); ++i) {
98 if (values[i].find(data_reduction_proxy::
99 kTamperDetectFingerprintChromeProxy) == 0) {
100 contains_tamper_detect_fingerprints = true;
101 // Save Chrome-Proxy fingerprint.
102 chrome_proxy_fingerprint = values[i].substr(strlen(data_reduction_proxy::
103 kTamperDetectFingerprintChromeProxy));
104 // Erase Chrome-Proxy's fingerprint from Chrome-Proxy header for
105 // later fingerprint calculation.
106 values.erase(values.begin() + i);
107 // Adjust index |i| after erasing.
108 --i;
109 }
110 else if (values[i].find(data_reduction_proxy::kTamperDetectFingerprint)
111 == 0) {
112 // Save other fingerprints.
113 other_fingerprints = values[i].substr(strlen(data_reduction_proxy::
114 kTamperDetectFingerprint));
115 }
116 }
117 return contains_tamper_detect_fingerprints;
118 }
119
120 void CheckResponseFingerprint(const net::HttpResponseHeaders* headers,
121 const bool is_secure_scheme)
122 {
123 std::vector<std::string> values = DataReductionProxyTamperDetect::
124 GetHeaderValues(headers, "Chrome-Proxy");
125
126 // |chrome_proxy_fingerprint| holds the value of fingerprint of
127 // Chrome-Proxy header.
128 // |other_fingerprints| holds the value of other fingerprints.
129 std::string chrome_proxy_fingerprint, other_fingerprints;
130
131 // Check if there are fingerprints (and thus need to detect tamper).
132 if (!ContainsTamperDetectFingerprints(values,
133 chrome_proxy_fingerprint,
134 other_fingerprints))
135 return;
136
137 // Found tamper detect request field.
138 // Get carrier ID.
139 unsigned mcc_mnc = 0;
140 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &mcc_mnc);
141
142 DataReductionProxyTamperDetect tamper_detect(headers, is_secure_scheme,
143 mcc_mnc, &values);
144
145 // Check if Chrome-Proxy header has been tampered.
146 if (tamper_detect.CheckHeaderChromeProxy(chrome_proxy_fingerprint)) {
147 UMA_REPORT(is_secure_scheme,
148 "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy",
149 "DataReductionProxy.HTTPHeaderTampered_ChromeProxy",
150 mcc_mnc);
151 return;
152 } else
153 UMA_REPORT(is_secure_scheme,
154 "DataReductionProxy.HTTPSHeaderTamperDetection",
155 "DataReductionProxy.HTTPHeaderTamperDetection",
156 mcc_mnc);
157
158 // Separate fingerprints from |other_fingerprints|.
159 net::HttpUtil::ValuesIterator it(other_fingerprints.begin(),
160 other_fingerprints.end(), '|');
161
162 // For each fingerprint, get its name |key| and the fingerprint value |value|
163 // from data reduction proxy. CheckReportFingerprint will handle the tamper
164 // detect and corresponding UMA report.
165 size_t delimiter_pos = std::string::npos;
166 while (it.GetNext()) {
167
168 delimiter_pos = it.value().find("=");
169 if (delimiter_pos == std::string::npos)
170 continue;
171 std::string key = it.value().substr(0, delimiter_pos);
172 std::string value = it.value().substr(delimiter_pos + 1);
173
174 FingerprintCode fingerprint_code = tamper_detect.GetFingerprintCode(key);
175 switch (fingerprint_code) {
176 case VIA:
177 if (tamper_detect.CheckHeaderVia(value))
178 tamper_detect.ReportHeaderVia();
179 break;
180 case OTHERHEADERS:
181 if (tamper_detect.CheckHeaderOtherHeaders(value))
182 tamper_detect.ReportHeaderOtherHeaders();
183 break;
184 case CONTENTLENGTH:
185 if (tamper_detect.CheckHeaderContentLength(value))
186 tamper_detect.ReportHeaderContentLength();
187 break;
188 case CHROMEPROXY:
189 case NONEXIST:
190 break;
191 }
192 }
193 return;
194 }
195
196 // It initialize the fingerprint tag to fingerprint code map.
197 // Right now we have 3 fingerprints to check (besides Chrome-Proxy header's
198 // fingerprint, which has been handled specially.
199 DataReductionProxyTamperDetect::DataReductionProxyTamperDetect(
200 const net::HttpResponseHeaders* headers, const bool secure,
201 const unsigned mcc_mnc_, std::vector<std::string>* values)
202 : response_headers(headers),
203 is_secure_scheme(secure),
204 mcc_mnc(mcc_mnc_),
205 clean_chrome_proxy_header_values(values) {
206 fingperprint_tag_code_map = std::map<std::string, FingerprintCode>();
207 fingperprint_tag_code_map[kTamperDetectFingerprintVia] = VIA;
208 fingperprint_tag_code_map[kTamperDetectFingerprintOther] = OTHERHEADERS;
209 fingperprint_tag_code_map[kTamperDetectFingerprintContengLength] =
210 CONTENTLENGTH;
211 };
212
213 DataReductionProxyTamperDetect::~DataReductionProxyTamperDetect() {};
214
215
216 // Utility function...
217 // Sort the strings in |values| alphabetically, concatenate them into a string.
218 std::string DataReductionProxyTamperDetect::ValuesToSortedString(
219 std::vector<std::string> &values) {
220 std::string aggregated_values;
221
222 std::sort(values.begin(), values.end());
223 for (size_t i = 0; i < values.size(); ++i)
224 aggregated_values += values[i] + ",";
225 return aggregated_values;
226 }
227
228 // Utility function...
229 // For a given string, calculate and return the MD5 hash value of the string.
230 std::string DataReductionProxyTamperDetect::GetMD5(const std::string &input) {
231 base::MD5Context context;
232 base::MD5Init(&context);
233 base::MD5Update(&context, input);
234 base::MD5Digest new_digest;
235 base::MD5Final(&new_digest, &context);
236 return std::string((char*)new_digest.a, ARRAYSIZE_UNSAFE(new_digest.a));
237 }
238
239 // Utility function...
240 // For a given |header_name|, get all its values and return the vector contains
241 // all of the values.
242 std::vector<std::string> DataReductionProxyTamperDetect::GetHeaderValues(
243 const net::HttpResponseHeaders* headers, const std::string& header_name) {
244 std::vector<std::string> values;
245 std::string value;
246 void* iter = NULL;
247 while (headers->EnumerateHeader(&iter, header_name, &value)) {
248 values.push_back(value);
249 }
250 return values;
251 }
252
253 FingerprintCode DataReductionProxyTamperDetect::GetFingerprintCode(
254 const std::string& tag) {
255 std::map<std::string, FingerprintCode>::iterator it =
256 fingperprint_tag_code_map.find(tag);
257
258 return it == fingperprint_tag_code_map.end() ? NONEXIST : it->second;
259 }
260
261
262
263 // Check whether Chrome-Proxy header has been tampered.
264 // |fingerprint| is the fingerprint Chrome received from data reduction proxy,
265 // which is Base64 encoded. Decode it first. Calculate the hash value of
266 // Chrome-Proxy header. Note that |clean_chrome_proxy_header_values| holds
267 // the values of Chrome-Proxy header with its own fingerprint removed,
268 // so it's the correct values to be used to calculate fingerprint.
269 // Compare calculated fingerprint to the fingerprint from data reduction proxy
270 // (the removed value) and see there is tamper detected.
271 bool DataReductionProxyTamperDetect::CheckHeaderChromeProxy(
272 const std::string& fingerprint) const {
273 std::string received_fingerprint;
274 if (!base::Base64Decode(fingerprint, &received_fingerprint))
275 return false;
276 // Calculate the MD5 hash value of Chrome-Proxy.
277 std::string actual_fingerprint = GetMD5(
278 ValuesToSortedString(*clean_chrome_proxy_header_values));
279
280 // Compare and check if there is tamper detected.
281 return received_fingerprint != actual_fingerprint;
282 }
283
284 // For Via header tamper detection...
285 // Check whether there are proxies/middleboxes between Chrome
286 // and data reduction proxy. Concretely, it checks whether there are other
287 // proxies/middleboxes' name after data reduction proxy's name in Via header.
288 bool DataReductionProxyTamperDetect::CheckHeaderVia(
289 const std::string& fingerprint) const {
290
291 std::vector<std::string> vias = GetHeaderValues(response_headers, "via");
292
293 // If there is no tag, then data reduction proxy's tag have been removed.
294 if (vias.size() == 0) return true;
295 // Check whether the last proxy/middlebox is data reduction proxy or not.
296
297 bool seen_data_reduction_proxy = false;
298 // change 2 to constant ... figure it out how to do it
299 for (int i = 0; i < 2; ++i) {
300 if (vias[vias.size() - 1].find(kDataReductionProxyViaValues[i]) !=
301 std::string::npos) {
302 seen_data_reduction_proxy = true;
303 break;
304 }
305 }
306
307 return !seen_data_reduction_proxy;
308 }
309
310 // Report Via header tamper detected.
311 void DataReductionProxyTamperDetect::ReportHeaderVia() const {
312 UMA_REPORT(is_secure_scheme,
313 "DataReductionProxy.HTTPSHeaderTampered_Via",
314 "DataReductionProxy.HTTPHeaderTampered_Via",
315 mcc_mnc);
316 }
317
318
319 // For other headers tamper detection...
320 // Check whether values of a predefined list of headers have been tampered.
321 // The format for |fingerprint| is:
322 // [base64fingerprint]:header_name1:header_namer2:...
323 // Firstly extract the header names in the |fingerprint|.
324 // For each header,
325 // 1) we get all the values of such header;
326 // 2) we sort the values alphabetically;
327 // 3) we concatenate sorted values to a string and calculate MD5 hash on it.
328 // Finally, we compare whether it equals to the fingerprint from
329 // data reduction proxy.
330 bool DataReductionProxyTamperDetect::CheckHeaderOtherHeaders(
331 const std::string& fingerprint) const {
332 std::string received_fingerprint;
333
334 net::HttpUtil::ValuesIterator it(fingerprint.begin(),
335 fingerprint.end(), ':');
336
337 // Make sure there is [base64fingerprint] and it can be decoded.
338 if (!(it.GetNext() &&
339 base::Base64Decode(std::string(it.value()), &received_fingerprint)))
340 return false;
341
342 std::string header_values;
343 // Enumerate the list of headers.
344 while (it.GetNext()) {
345 // Get values of one header.
346 std::vector<std::string> values =
347 GetHeaderValues(response_headers, std::string(it.value()));
348 // Sort the values and concatenate them.
349 header_values += ValuesToSortedString(values) + ";";
350 }
351
352 // Calculate MD5 hash value on the concatenated string.
353 std::string actual_fingerprint = GetMD5(header_values);
354
355 return received_fingerprint != actual_fingerprint;
356 }
357
358 // Report other headers tamper detected.
359 void DataReductionProxyTamperDetect::ReportHeaderOtherHeaders() const {
360 UMA_REPORT(is_secure_scheme,
361 "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders",
362 "DataReductionProxy.HTTPHeaderTampered_OtherHeaders",
363 mcc_mnc);
364 }
365
366
367 // For Content-Length tamper detection...
368 // Check whether the Content-Length value is different from what
369 // data reduction proxy sees. This is an indicator that the response body
370 // have been modified.
371 // It's modified only if we can decode Content-Length numbers at both end
372 // and such two numbers are not equal.
373 bool DataReductionProxyTamperDetect::CheckHeaderContentLength(
374 const std::string& fingerprint) const {
375 int received_content_length, actual_content_length;
376 // If Content-Length value from data reduction proxy is not available or
377 // it cannot be converted to integer, pass.
378 if (base::StringToInt(fingerprint, &received_content_length)) {
379 std::string actual_content_length_;
380 // If there is Content-Length at Chrome client side is not available, pass.
381 if (response_headers->GetNormalizedHeader("Content-Length",
382 &actual_content_length_)) {
383 // If the Content-Length value cannot be converted to integer,
384 // i.e., not valid, pass.
385 if (!base::StringToInt(actual_content_length_, &actual_content_length))
386 return false;
387 return received_content_length != actual_content_length;
388 }
389 }
390 return false;
391 }
392
393 // Report Content-Length tamper detected.
394 // Get MIME type of the response and report to different UMA histogram.
395 // Right now MIME types contain JavaScript, CSS, Images, and others.
396 void DataReductionProxyTamperDetect::ReportHeaderContentLength() const {
397 std::string mime_type;
398 // Get MIME type.
399 response_headers->GetMimeType(&mime_type);
400 UMA_REPORT(is_secure_scheme,
401 "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
402 "DataReductionProxy.HTTPHeaderTampered_ContentLength",
403 mcc_mnc);
404
405 // Report tampered JavaScript.
406 if (mime_type.compare("text/javascript") == 0 ||
407 mime_type.compare("application/x-javascript") == 0 ||
408 mime_type.compare("application/javascript") == 0)
409 UMA_REPORT(is_secure_scheme,
410 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS",
411 "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS",
412 mcc_mnc);
413 // Report tampered CSSs.
414 else if (mime_type.compare("text/css") == 0)
415 UMA_REPORT(is_secure_scheme,
416 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS",
417 "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS",
418 mcc_mnc);
419 // Report tampered images.
420 else if (mime_type.find("image") == 0)
421 UMA_REPORT(is_secure_scheme,
422 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image",
423 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image",
424 mcc_mnc);
425 // Report tampered other MIME types.
426 else
427 UMA_REPORT(is_secure_scheme,
428 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other",
429 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other",
430 mcc_mnc);
431 }
432 } // namespace data_reduction_proxy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698