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

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 #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. First reports to either |https_histogram| or
bolian 2014/07/19 00:15:24 Can we rewrite the comment shorter. Reading the co
xingx 2014/07/21 18:51:44 Done.
21 // |http_histogram| depending on |scheme_is_https|, with |carrier_id| as
22 // bucket, which counts for each carrier, how many responses have been tampered
23 // with. Then reports to |http(s)_histogram|_Total, which counts the total
24 // number of responses that have been tampered with across carriers.
25 #define REPORT_TAMPER_DETECTION_UMA(scheme_is_https, http_histogram, https_histo gram, carrier_id) \
26 do { \
27 if (scheme_is_https) { \
28 UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, carrier_id); \
29 UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
30 } else { \
31 UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, carrier_id); \
32 UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
33 }\
34 } while (0)
35
36 namespace data_reduction_proxy {
37
38 // static
39 void DataReductionProxyTamperDetection::CheckResponseFingerprint(
bolian 2014/07/19 00:15:24 can we rename this to DetectAndReport?
xingx 2014/07/21 18:51:44 Done.
40 const net::HttpResponseHeaders* headers,
41 const bool is_secure_scheme) {
42 DCHECK(headers);
43 if (!headers)
44 return;
45
46 // If fingerprint of Chrome-Proxy header is absent, abort tamper detection.
bengr 2014/07/18 22:47:54 If the fingerprint of the Chrome-Proxy
xingx 2014/07/21 18:51:45 Done.
47 std::string chrome_proxy_fingerprint;
48 if (!GetDataReductionProxyActionValue(
49 headers,
50 tamper_detection_fingerprint_names::kFingerprintChromeProxy,
bolian 2014/07/19 00:15:24 why add a new namespace just for this?
xingx 2014/07/21 18:51:45 Done.
51 &chrome_proxy_fingerprint))
52 return;
53
54 // Gets Chrome-Proxy header values.
55 std::vector<std::string> chrome_proxy_header_values =
56 GetHeaderValues(headers, "Chrome-Proxy");
57
58 // Removes Chrome-Proxy header's fingerprint for generating the fingerprint
bengr 2014/07/18 22:47:54 Remove the Chrome-Proxy
xingx 2014/07/21 18:51:45 Done.
59 // of received Chrome-Proxy header later.
bengr 2014/07/18 22:47:54 I don't understand this comment. Do you mean: //
bolian 2014/07/19 00:15:24 +1. Better document why than what.
60 RemoveChromeProxyFingerprint(&chrome_proxy_header_values);
61
62 // Get carrier ID.
63 unsigned carrier_id = 0;
64 #if defined(OS_ANDROID)
65 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &carrier_id);
66 #endif
67
68 DataReductionProxyTamperDetection tamper_detection(
69 headers,
70 is_secure_scheme,
71 carrier_id,
72 &chrome_proxy_header_values);
73
74 // Checks if Chrome-Proxy header has been tampered with.
75 if (tamper_detection.IsChromeProxyHeaderTampered(chrome_proxy_fingerprint)) {
76 tamper_detection.ReportChromeProxyHeaderTamperedUMA();
77 return;
78 } else {
bolian 2014/07/19 00:15:24 get rid of else. you have returned in 'if'.
xingx 2014/07/21 18:51:44 Done.
79 // Chrome-Proxy header has not been tampered with, reports the number of
80 // 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
88 // Checks other fingerprints.
89 const char* fingerprint_names[] = {
bolian 2014/07/19 00:15:24 You don't need this array. Just iterate the name->
xingx 2014/07/21 18:51:44 Done. Good point.
90 data_reduction_proxy::tamper_detection_fingerprint_names::
bengr 2014/07/18 22:47:54 Call this kChromeProxyActionFingerprintVia. Don't
xingx 2014/07/21 18:51:45 Done.
91 kFingerprintVia,
92 data_reduction_proxy::tamper_detection_fingerprint_names::
93 kFingerprintOtherHeaders,
94 data_reduction_proxy::tamper_detection_fingerprint_names::
95 kFingerprintContentLength
96 };
97
98 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(fingerprint_names); ++i) {
99 std::string fingerprint;
100 if (!GetDataReductionProxyActionValue(
101 headers, fingerprint_names[i], &fingerprint)) {
102 continue;
103 }
104
105 FingerprintCode fingerprint_code = tamper_detection.
bengr 2014/07/18 22:47:54 move tamper_detection to the next line.
xingx 2014/07/21 18:51:45 Removed.
106 GetFingerprintCode(fingerprint_names[i]);
107 switch (fingerprint_code) {
108 case VIA:
109 if (tamper_detection.IsViaHeaderTampered(fingerprint))
110 tamper_detection.ReportViaHeaderTamperedUMA();
111 break;
112 case OTHERHEADERS:
113 if (tamper_detection.AreOtherHeadersTampered(fingerprint))
114 tamper_detection.ReportOtherHeadersTamperedUMA();
115 break;
116 case CONTENTLENGTH:
117 if (tamper_detection.IsContentLengthHeaderTampered(fingerprint))
118 tamper_detection.ReportContentLengthHeaderTamperedUMA();
119 break;
120 default:
121 NOTREACHED();
122 break;
123 }
124 }
125 return;
bolian 2014/07/19 00:15:24 rm
xingx 2014/07/21 18:51:45 Done.
126 }
127
128 // Constructor initializes fingerprint name to code map.
bengr 2014/07/18 22:47:54 // Constructor initializes the map of fingerprint
xingx 2014/07/21 18:51:45 Done.
129 DataReductionProxyTamperDetection::DataReductionProxyTamperDetection(
130 const net::HttpResponseHeaders* headers,
131 const bool is_secure,
132 const unsigned carrier_id,
133 std::vector<std::string>* values)
134 : response_headers_(headers),
135 is_secure_scheme_(is_secure),
136 carrier_id_(carrier_id),
137 clean_chrome_proxy_header_values_(values) {
138 DCHECK(headers);
139 fingerprint_name_code_map_ = std::map<std::string, FingerprintCode>();
140 fingerprint_name_code_map_
141 [tamper_detection_fingerprint_names::kFingerprintVia] = VIA;
142 fingerprint_name_code_map_
143 [tamper_detection_fingerprint_names::kFingerprintOtherHeaders] =
144 OTHERHEADERS;
145 fingerprint_name_code_map_
146 [tamper_detection_fingerprint_names::kFingerprintContentLength] =
147 CONTENTLENGTH;
148 };
149
150 DataReductionProxyTamperDetection::~DataReductionProxyTamperDetection() {};
151
152 // Checks whether the Chrome-Proxy header has been tampered with. |fingerprint|
153 // is the fingerprint received from the data reduction proxy, which is Base64
154 // encoded. Decodes it first. Then calculates the fingerprint of received
155 // Chrome-Proxy header, and compares the two to see whether they are equal or
156 // not. Note that |clean_chrome_proxy_header_values_| holds the values of
157 // Chrome-Proxy header with its own fingerprint removed, so it's the correct
158 // values to calculate fingerprint of received Chrome-Proxy header.
159 bool DataReductionProxyTamperDetection::IsChromeProxyHeaderTampered(
160 const std::string& fingerprint) const {
161 std::string received_fingerprint;
162 if (!base::Base64Decode(fingerprint, &received_fingerprint))
163 return true;
164 // Calculates the MD5 hash value of Chrome-Proxy.
165 std::string actual_fingerprint = GetMD5(
166 ValuesToSortedString(clean_chrome_proxy_header_values_));
167
168 return received_fingerprint != actual_fingerprint;
169 }
170
171 void DataReductionProxyTamperDetection::ReportChromeProxyHeaderTamperedUMA()
172 const {
173 REPORT_TAMPER_DETECTION_UMA(
174 is_secure_scheme_,
175 "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy",
176 "DataReductionProxy.HTTPHeaderTampered_ChromeProxy",
177 carrier_id_);
178 }
179
180 // Checks whether there are other proxies/middleboxes' name after the data
181 // reduction proxy's name in Via header.
182 bool DataReductionProxyTamperDetection::IsViaHeaderTampered(
183 const std::string& fingerprint) const {
184 bool has_chrome_proxy, has_intermediary;
185 has_chrome_proxy = HasDataReductionProxyViaHeader(response_headers_,
186 &has_intermediary);
187 // Via header of the data reduction proxy is missing.
188 if (!has_chrome_proxy) {
bolian 2014/07/19 00:15:24 I would move this to the report function and retur
xingx 2014/07/21 18:51:44 Done.
189 REPORT_TAMPER_DETECTION_UMA(
190 is_secure_scheme_,
191 "DataReductionProxy.HTTPSHeaderTampered_Via_Missing",
192 "DataReductionProxy.HTTPHeaderTampered_Via_Missing",
193 carrier_id_);
194 return false;
195 }
196 return !has_intermediary;
197 }
198
199 void DataReductionProxyTamperDetection::ReportViaHeaderTamperedUMA() const {
200 REPORT_TAMPER_DETECTION_UMA(is_secure_scheme_,
201 "DataReductionProxy.HTTPSHeaderTampered_Via",
202 "DataReductionProxy.HTTPHeaderTampered_Via",
203 carrier_id_);
204 }
205
206 // Checks whether values of a predefined list of headers have been modified.
207 // The format for |fingerprint| is:
bengr 2014/07/18 22:47:53 I'm confused by this. I would expect an explanatio
xingx 2014/07/21 18:51:45 Done. the function to generate "canonical represe
208 // [base64encoded_fingerprint]:header_name1:header_namer2:...
bengr 2014/07/18 22:47:54 Verify that the delimiter is legal according to RF
xingx 2014/07/21 18:51:44 Done.
209 // Firstly extracts the header names in the |fingerprint|. For each header,
210 // generates a string of all the values of such header. Concatenates the
211 // strings for all the headers (with delimiter ";") and calculates the MD5 hash
212 // value on it. Compares such hash value to the fingerprint received from the
213 // data reduction proxy.
214 bool DataReductionProxyTamperDetection::AreOtherHeadersTampered(
215 const std::string& fingerprint) const {
216 std::string received_fingerprint;
217 DCHECK(fingerprint.size());
218
219 // ":" delimiter would not occur in base64 as well as header names.
220 net::HttpUtil::ValuesIterator it(fingerprint.begin(),
221 fingerprint.end(), ':');
222
223 // The first value from fingerprint is the base64 encoded fingerprint; the
224 // following values are the header names included in fingerprint calculation.
225 // Make sure there is [base64fingerprint] and it can be decoded.
226 if (!(it.GetNext() &&
227 base::Base64Decode(it.value(), &received_fingerprint))) {
228 NOTREACHED();
229 return true;
230 }
231
232 std::string header_values;
233 // Enumerates the list of headers.
234 while (it.GetNext()) {
235 // Gets values of one header.
236 std::vector<std::string> response_header_values =
237 GetHeaderValues(response_headers_, it.value());
238 // Sorts the values and concatenate them, with delimiter ";". ";" would not
239 // occur in header values,
240 header_values += ValuesToSortedString(&response_header_values) + ";";
bengr 2014/07/18 22:47:54 The end of the list will be a ';'. Is that ok?
xingx 2014/07/21 18:51:45 That's expected, so a non-exist header is differen
241 }
242
243 // Calculates MD5 hash value on the concatenated string.
bengr 2014/07/18 22:47:54 the MD5 hash of
xingx 2014/07/21 18:51:45 Done.
244 std::string actual_fingerprint = GetMD5(header_values);
245
246 return received_fingerprint != actual_fingerprint;
247 }
248
249 void DataReductionProxyTamperDetection::ReportOtherHeadersTamperedUMA() const {
250 REPORT_TAMPER_DETECTION_UMA(
251 is_secure_scheme_,
252 "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders",
253 "DataReductionProxy.HTTPHeaderTampered_OtherHeaders",
254 carrier_id_);
255 }
256
257 // Checks whether the Content-Length value is different from what the data
258 // reduction proxy sends. Reports it as modified only if Content-Length can be
259 // decoded as an integer at both ends and such two numbers are not equal.
260 bool DataReductionProxyTamperDetection::IsContentLengthHeaderTampered(
261 const std::string& fingerprint) const {
262 int received_content_length_fingerprint, actual_content_length;
263 // If Content-Length value from data reduction proxy does not exist or it
264 // cannot be converted to an integer, abort.
265 if (base::StringToInt(fingerprint, &received_content_length_fingerprint)) {
266 std::string actual_content_length_string;
267 // If there is no Content-Length header received, abort.
268 if (response_headers_->GetNormalizedHeader("Content-Length",
269 &actual_content_length_string)) {
270 // If the Content-Length value cannot be converted to integer, abort.
271 if (!base::StringToInt(actual_content_length_string,
272 &actual_content_length)) {
273 return false;
274 }
275
276 return received_content_length_fingerprint != actual_content_length;
277 }
278 }
279 return false;
280 }
281
282 void DataReductionProxyTamperDetection::ReportContentLengthHeaderTamperedUMA()
283 const {
284 // Gets MIME type of the response and reports to UMA histograms separately.
285 // Divides MIME types into 4 groups: JavaScript, CSS, Images, and others.
286 REPORT_TAMPER_DETECTION_UMA(
287 is_secure_scheme_,
288 "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
289 "DataReductionProxy.HTTPHeaderTampered_ContentLength",
290 carrier_id_);
291
292 // Gets MIME type.
293 std::string mime_type;
294 response_headers_->GetMimeType(&mime_type);
295
296 // Reports tampered JavaScript.
297 if (mime_type.compare("text/javascript") == 0 ||
298 mime_type.compare("application/x-javascript") == 0 ||
299 mime_type.compare("application/javascript") == 0) {
300 REPORT_TAMPER_DETECTION_UMA(
301 is_secure_scheme_,
302 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS",
303 "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS",
304 carrier_id_);
305 }
306 // Reports tampered CSSs.
307 else if (mime_type.compare("text/css") == 0) {
308 REPORT_TAMPER_DETECTION_UMA(
309 is_secure_scheme_,
310 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS",
311 "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS",
312 carrier_id_);
313 }
314 // Reports tampered images.
315 else if (mime_type.find("image") == 0) {
bolian 2014/07/19 00:15:24 image/
xingx 2014/07/21 18:51:45 Done.
316 REPORT_TAMPER_DETECTION_UMA(
317 is_secure_scheme_,
318 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image",
319 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image",
320 carrier_id_);
321 }
322 // Reports tampered other MIME types.
323 else {
324 REPORT_TAMPER_DETECTION_UMA(
325 is_secure_scheme_,
326 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other",
327 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other",
328 carrier_id_);
329 }
330 }
331
332 DataReductionProxyTamperDetection::FingerprintCode
333 DataReductionProxyTamperDetection::GetFingerprintCode(
334 const std::string& fingerprint_name) {
335 std::map<std::string, FingerprintCode>::iterator it =
336 fingerprint_name_code_map_.find(fingerprint_name);
337
338 return it == fingerprint_name_code_map_.end() ? NONEXIST : it->second;
bengr 2014/07/18 22:47:53 This is confusing. How about: if (!fingerprint_nam
xingx 2014/07/21 18:51:45 Done.
339 }
340
341 // Removes Chrome-Proxy header's fingerprint (action name
342 // |kFingerprintChromeProxy|) from its values vector.
343 void DataReductionProxyTamperDetection::RemoveChromeProxyFingerprint(
344 std::vector<std::string>* values) {
bengr 2014/07/18 22:47:54 Check that values is not NULL
xingx 2014/07/21 18:51:44 Done.
345 std::string chrome_proxy_fingerprint_prefix = std::string(
346 tamper_detection_fingerprint_names::kFingerprintChromeProxy) + "=";
347
348 size_t size = values->size();
bengr 2014/07/18 22:47:54 Move values->size() into the loop declaration.
xingx 2014/07/21 18:51:44 Done.
349 for (size_t i = 0; i < size; ++i) {
350 if ((*values)[i].find(chrome_proxy_fingerprint_prefix) == 0) {
351 values->erase(values->begin() + i);
352 break;
353 }
354 }
355 }
356
357 std::string DataReductionProxyTamperDetection::ValuesToSortedString(
358 std::vector<std::string>* values) {
359 std::string concatenated_values;
360
361 std::sort(values->begin(), values->end());
362 size_t size = values->size();
bengr 2014/07/18 22:47:53 Move values->size() into the loop declaration.
xingx 2014/07/21 18:51:45 Done.
363 for (size_t i = 0; i < size; ++i)
bengr 2014/07/18 22:47:54 add curly brace
xingx 2014/07/21 18:51:45 Done.
364 // Concatenates with delimiter ",".
365 concatenated_values += (*values)[i] + ",";
366 return concatenated_values;
367 }
368
369 std::string DataReductionProxyTamperDetection::GetMD5(
370 const std::string &input) {
371 base::MD5Digest digest;
372 base::MD5Sum(input.c_str(), input.size(), &digest);
373 return std::string((char*)digest.a, ARRAYSIZE_UNSAFE(digest.a));
374 }
375
376 std::vector<std::string> DataReductionProxyTamperDetection::GetHeaderValues(
377 const net::HttpResponseHeaders* headers, const std::string& header_name) {
378 std::vector<std::string> values;
379 std::string value;
380 void* iter = NULL;
381 while (headers->EnumerateHeader(&iter, header_name, &value)) {
382 values.push_back(value);
383 }
384 return values;
385 }
386
387 } // namespace data_reduction_proxy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698