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

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

Powered by Google App Engine
This is Rietveld 408576698