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

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, 4 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 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_de tect.h"
6
7 #include <algorithm>
8 #include <cstring>
9
10 #include "base/base64.h"
11 #include "base/md5.h"
12 #include "base/metrics/histogram.h"
13 #include "base/metrics/sparse_histogram.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
16 #include "net/android/network_library.h"
17 #include "net/http/http_response_headers.h"
18 #include "net/http/http_util.h"
19
20 // Macro for UMA reporting. Depending on |scheme_is_https|, first reports to
bolian 2014/07/24 21:00:15 s/first reports to/it first reports/
xingx1 2014/07/25 20:32:15 Done.
21 // histogram events |https_histogram| or |http_histogram| by |carrier_id|; then
22 // reports total counts to |https_histogram|_Total or |http_histogram|_Total.
23 #define REPORT_TAMPER_DETECTION_UMA(scheme_is_https, http_histogram, https_histo gram, carrier_id) \
24 do { \
25 if (scheme_is_https) { \
26 UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, carrier_id); \
27 UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
28 } else { \
29 UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, carrier_id); \
30 UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
31 }\
32 } while (0)
33
34 namespace data_reduction_proxy {
35
36 // static
37 bool DataReductionProxyTamperDetection::DetectAndReport(
38 const net::HttpResponseHeaders* headers,
39 const bool is_secure_scheme) {
40 DCHECK(headers);
41 if (!headers)
42 return false;
43
44 // If the fingerprint of the Chrome-Proxy header is absent, abort tamper
45 // detection.
46 std::string chrome_proxy_fingerprint;
47 if (!GetDataReductionProxyActionValue(
48 headers,
49 kChromeProxyActionFingerprintChromeProxy,
50 &chrome_proxy_fingerprint))
51 return false;
52
53 // Gets the Chrome-Proxy header values.
54 std::vector<std::string> chrome_proxy_header_values =
55 GetHeaderValues(headers, "Chrome-Proxy");
56
57 // Removes header's fingerprint for generating the fingerprint of received
58 // Chrome-Proxy header later.
59 RemoveChromeProxyFingerprint(&chrome_proxy_header_values);
60
61 // Get carrier ID.
62 unsigned carrier_id = 0;
63 #if defined(OS_ANDROID)
64 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &carrier_id);
65 #endif
66
67 DataReductionProxyTamperDetection tamper_detection(
68 headers,
69 is_secure_scheme,
70 carrier_id,
71 &chrome_proxy_header_values);
72
73 // Checks if the Chrome-Proxy header has been tampered with.
74 if (tamper_detection.IsChromeProxyHeaderTampered(chrome_proxy_fingerprint)) {
75 tamper_detection.ReportChromeProxyHeaderTamperedUMA();
76 return true;
77 }
78
79 // Since the Chrome-Proxy header has not been tampered with, reports the
80 // number of responses that other fingerprints will be checked.
81 REPORT_TAMPER_DETECTION_UMA(
82 is_secure_scheme,
83 "DataReductionProxy.HTTPSHeaderTamperDetection",
84 "DataReductionProxy.HTTPHeaderTamperDetection",
85 carrier_id);
86
87 bool tampered = false;
88 std::map<std::string, FingerprintCode>::iterator i;
89 for (i = tamper_detection.fingerprint_name_code_map_.begin();
90 i != tamper_detection.fingerprint_name_code_map_.end(); ++i) {
91 std::string fingerprint;
92 if (!GetDataReductionProxyActionValue(
93 headers, i->first, &fingerprint)) {
94 continue;
95 }
96
97 switch (i->second) {
98 case VIA:
99 bool has_chrome_proxy_via_header;
100 if (tamper_detection.IsViaHeaderTampered(
101 fingerprint, &has_chrome_proxy_via_header)) {
102 tamper_detection.ReportViaHeaderTamperedUMA(
103 has_chrome_proxy_via_header);
104 tampered = true;
105 }
106 break;
107 case OTHERHEADERS:
108 if (tamper_detection.AreOtherHeadersTampered(fingerprint)) {
109 tamper_detection.ReportOtherHeadersTamperedUMA();
110 tampered = true;
111 }
112 break;
113 case CONTENTLENGTH:
114 if (tamper_detection.IsContentLengthHeaderTampered(fingerprint)) {
115 tamper_detection.ReportContentLengthHeaderTamperedUMA();
116 tampered = true;
117 }
118 break;
119 default:
120 NOTREACHED();
121 break;
122 }
123 }
124 return tampered;
125 }
126
127 // Constructor initializes the map of fingerprint name to code.
128 DataReductionProxyTamperDetection::DataReductionProxyTamperDetection(
129 const net::HttpResponseHeaders* headers,
130 const bool is_secure,
131 const unsigned carrier_id,
132 std::vector<std::string>* values)
133 : response_headers_(headers),
134 is_secure_scheme_(is_secure),
135 carrier_id_(carrier_id),
136 clean_chrome_proxy_header_values_(values) {
137 DCHECK(headers);
138 fingerprint_name_code_map_ = std::map<std::string, FingerprintCode>();
139 fingerprint_name_code_map_
140 [kChromeProxyActionFingerprintVia] = VIA;
141 fingerprint_name_code_map_
142 [kChromeProxyActionFingerprintOtherHeaders] = OTHERHEADERS;
143 fingerprint_name_code_map_
144 [kChromeProxyActionFingerprintContentLength] = CONTENTLENGTH;
145 };
146
147 DataReductionProxyTamperDetection::~DataReductionProxyTamperDetection() {};
148
149 // Checks whether the Chrome-Proxy header has been tampered with. |fingerprint|
150 // is the fingerprint received from the data reduction proxy, which is Base64
151 // encoded. Decodes it first. Then calculates the fingerprint of received
152 // Chrome-Proxy header, and compares the two to see whether they are equal or
153 // not. Note that |clean_chrome_proxy_header_values_| holds the values of the
154 // Chrome-Proxy header with its own fingerprint removed, so it's the correct
155 // values to calculate fingerprint of received Chrome-Proxy header.
156 bool DataReductionProxyTamperDetection::IsChromeProxyHeaderTampered(
157 const std::string& fingerprint) const {
158 std::string received_fingerprint;
159 if (!base::Base64Decode(fingerprint, &received_fingerprint))
160 return true;
161 // Calculates the MD5 hash value of Chrome-Proxy.
162 std::string actual_fingerprint = GetMD5(
163 ValuesToSortedString(clean_chrome_proxy_header_values_));
164
165 return received_fingerprint != actual_fingerprint;
166 }
167
168 void DataReductionProxyTamperDetection::ReportChromeProxyHeaderTamperedUMA()
169 const {
170 REPORT_TAMPER_DETECTION_UMA(
171 is_secure_scheme_,
172 "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy",
173 "DataReductionProxy.HTTPHeaderTampered_ChromeProxy",
174 carrier_id_);
175 }
176
177 // Checks whether there are other proxies/middleboxes' name after the data
178 // reduction proxy's name in Via header. |has_chrome_proxy_via_header| marks
179 // that whether the data reduction proxy's Via header occurs or not.
180 bool DataReductionProxyTamperDetection::IsViaHeaderTampered(
181 const std::string& fingerprint, bool* has_chrome_proxy_via_header) const {
182 bool has_intermediary;
183 *has_chrome_proxy_via_header = HasDataReductionProxyViaHeader(
184 response_headers_,
185 &has_intermediary);
186
187 if (*has_chrome_proxy_via_header)
188 return !has_intermediary;
189 return false;
190 }
191
192 void DataReductionProxyTamperDetection::ReportViaHeaderTamperedUMA(
193 bool has_chrome_proxy) const {
194 // The Via header of the data reduction proxy is missing.
195 if (!has_chrome_proxy) {
196 REPORT_TAMPER_DETECTION_UMA(
197 is_secure_scheme_,
198 "DataReductionProxy.HTTPSHeaderTampered_Via_Missing",
199 "DataReductionProxy.HTTPHeaderTampered_Via_Missing",
200 carrier_id_);
201 return;
202 }
203
204 REPORT_TAMPER_DETECTION_UMA(
205 is_secure_scheme_,
206 "DataReductionProxy.HTTPSHeaderTampered_Via",
207 "DataReductionProxy.HTTPHeaderTampered_Via",
208 carrier_id_);
209 }
210
211 // Checks whether values of a predefined list of headers have been modified. At
212 // the data reduction proxy side, it constructs a canonical representation of
213 // values of a list headers. The fingerprint is constructed as follows:
214 // 1) for each header, gets the string representation of its values (same to
215 // ValuesToSortedString);
216 // 2) concatenates all header's string representation with a ";" delimiter,
217 // respect to the order of the header list;
218 // 3) calculates the MD5 hash value of above concatenated string;
219 // 4) appends the header names to the fingerprint, with a delimiter "|".
220 // The constructed fingerprint looks like:
221 // [hashed_fingerprint]|header_name1|header_namer2:...
222 //
223 // To check whether such fingerprint matches the response that the Chromium
224 // client receives, the Chromium client firstly extracts the header names. For
225 // each header, gets its string representation (by ValuesToSortedString),
226 // concatenates them and calculates the MD5 hash value. Compares such hash
227 // value to the fingerprint received from the data reduction proxy.
228 bool DataReductionProxyTamperDetection::AreOtherHeadersTampered(
229 const std::string& fingerprint) const {
230 std::string received_fingerprint;
231 DCHECK(fingerprint.size());
232
233 // "|" delimiter would not occur in base64 as well as header names.
234 net::HttpUtil::ValuesIterator it(fingerprint.begin(),
235 fingerprint.end(), '|');
236
237 // The first value from fingerprint is the base64 encoded fingerprint; the
238 // following values are the header names included in fingerprint calculation.
239 // Make sure there is [base64fingerprint] and it can be decoded.
240 if (!(it.GetNext() &&
241 base::Base64Decode(it.value(), &received_fingerprint))) {
242 NOTREACHED();
243 return true;
244 }
245
246 std::string header_values;
247 // Enumerates the list of headers.
248 while (it.GetNext()) {
249 // Gets values of one header.
250 std::vector<std::string> response_header_values =
251 GetHeaderValues(response_headers_, it.value());
252 // Sorts the values and concatenate them, with delimiter ";". ";" would not
253 // occur in header values,
254 header_values += ValuesToSortedString(&response_header_values) + ";";
255 }
256
257 // Calculates the MD5 hash of the concatenated string.
258 std::string actual_fingerprint = GetMD5(header_values);
259
260 return received_fingerprint != actual_fingerprint;
261 }
262
263 void DataReductionProxyTamperDetection::ReportOtherHeadersTamperedUMA() const {
264 REPORT_TAMPER_DETECTION_UMA(
265 is_secure_scheme_,
266 "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders",
267 "DataReductionProxy.HTTPHeaderTampered_OtherHeaders",
268 carrier_id_);
269 }
270
271 // Checks whether the Content-Length value is different from what the data
272 // reduction proxy sends. Reports it as modified only if Content-Length can be
273 // decoded as an integer at both ends and such two numbers are not equal.
274 bool DataReductionProxyTamperDetection::IsContentLengthHeaderTampered(
275 const std::string& fingerprint) const {
276 int received_content_length_fingerprint, actual_content_length;
277 // If Content-Length value from data reduction proxy does not exist or it
278 // cannot be converted to an integer, abort.
279 if (base::StringToInt(fingerprint, &received_content_length_fingerprint)) {
280 std::string actual_content_length_string;
281 // If there is no Content-Length header received, abort.
282 if (response_headers_->GetNormalizedHeader("Content-Length",
283 &actual_content_length_string)) {
284 // If the Content-Length value cannot be converted to integer, abort.
285 if (!base::StringToInt(actual_content_length_string,
286 &actual_content_length)) {
287 return false;
288 }
289
290 return received_content_length_fingerprint != actual_content_length;
291 }
292 }
293 return false;
294 }
295
296 void DataReductionProxyTamperDetection::ReportContentLengthHeaderTamperedUMA()
297 const {
298 // Gets MIME type of the response and reports to UMA histograms separately.
299 // Divides MIME types into 4 groups: JavaScript, CSS, Images, and others.
300 REPORT_TAMPER_DETECTION_UMA(
301 is_secure_scheme_,
302 "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
303 "DataReductionProxy.HTTPHeaderTampered_ContentLength",
304 carrier_id_);
305
306 // Gets MIME type.
307 std::string mime_type;
308 response_headers_->GetMimeType(&mime_type);
309
310 // Reports tampered JavaScript.
311 if (mime_type.compare("text/javascript") == 0 ||
312 mime_type.compare("application/x-javascript") == 0 ||
313 mime_type.compare("application/javascript") == 0) {
314 REPORT_TAMPER_DETECTION_UMA(
315 is_secure_scheme_,
316 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS",
317 "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS",
318 carrier_id_);
319 }
320 // Reports tampered CSSs.
321 else if (mime_type.compare("text/css") == 0) {
322 REPORT_TAMPER_DETECTION_UMA(
323 is_secure_scheme_,
324 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS",
325 "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS",
326 carrier_id_);
327 }
328 // Reports tampered images.
329 else if (mime_type.find("image/") == 0) {
330 REPORT_TAMPER_DETECTION_UMA(
331 is_secure_scheme_,
332 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image",
333 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image",
334 carrier_id_);
335 }
336 // Reports tampered other MIME types.
337 else {
338 REPORT_TAMPER_DETECTION_UMA(
339 is_secure_scheme_,
340 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other",
341 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other",
342 carrier_id_);
343 }
344 }
345
346 DataReductionProxyTamperDetection::FingerprintCode
347 DataReductionProxyTamperDetection::GetFingerprintCode(
348 const std::string& fingerprint_name) {
349 std::map<std::string, FingerprintCode>::iterator it =
350 fingerprint_name_code_map_.find(fingerprint_name);
351
352 if (it != fingerprint_name_code_map_.end())
353 return it->second;
354 return NONEXIST;
355 }
356
357 // Removes the Chrome-Proxy header's fingerprint (action name
358 // |kFingerprintChromeProxy|) from its values vector.
359 void DataReductionProxyTamperDetection::RemoveChromeProxyFingerprint(
360 std::vector<std::string>* values) {
361 DCHECK(values);
362 if (!values) return;
363
364 std::string chrome_proxy_fingerprint_prefix = std::string(
365 kChromeProxyActionFingerprintChromeProxy) + "=";
366
367 for (size_t i = 0; i < values->size(); ++i) {
368 if ((*values)[i].find(chrome_proxy_fingerprint_prefix) == 0) {
369 values->erase(values->begin() + i);
370 break;
371 }
372 }
373 }
374
375 // We construct a canonical representation of the header so that reordered
376 // header values will produce the same fingerprint. The fingerprint is
377 // constructed as follows:
378 // 1) sorts the values;
379 // 2) concatenates sorted values with a "," delimiter.
380 std::string DataReductionProxyTamperDetection::ValuesToSortedString(
381 std::vector<std::string>* values) {
382 std::string concatenated_values;
383 DCHECK(values);
384 if (!values) return "";
385
386 std::sort(values->begin(), values->end());
387 for (size_t i = 0; i < values->size(); ++i) {
388 // Concatenates with delimiter ",".
389 concatenated_values += (*values)[i] + ",";
390 }
391 return concatenated_values;
392 }
393
394 std::string DataReductionProxyTamperDetection::GetMD5(
395 const std::string &input) {
396 base::MD5Digest digest;
397 base::MD5Sum(input.c_str(), input.size(), &digest);
398 return std::string((char*)digest.a, ARRAYSIZE_UNSAFE(digest.a));
399 }
400
401 std::vector<std::string> DataReductionProxyTamperDetection::GetHeaderValues(
402 const net::HttpResponseHeaders* headers, const std::string& header_name) {
403 std::vector<std::string> values;
404 std::string value;
405 void* iter = NULL;
406 while (headers->EnumerateHeader(&iter, header_name, &value)) {
407 values.push_back(value);
408 }
409 return values;
410 }
411
412 } // namespace data_reduction_proxy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698