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

Side by Side Diff: components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.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 tection.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/http/http_response_headers.h"
17 #include "net/http/http_util.h"
18
19 #if defined(OS_ANDROID)
20 #include "net/android/network_library.h"
21 #endif
22
23 // Macro for UMA reporting. HTTP response first reports to histogram events
24 // |http_histogram| by |carrier_id|; then reports the total counts to
25 // |http_histogram|_Total. HTTPS response reports to histograms
26 // |https_histogram| and |https_histogram|_Total similarly.
27 #define REPORT_TAMPER_DETECTION_UMA(scheme_is_https, http_histogram, https_histo gram, carrier_id) \
28 do { \
29 if (scheme_is_https) { \
30 UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, carrier_id); \
31 UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
32 } else { \
33 UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, carrier_id); \
34 UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
35 }\
36 } while (0)
37
38 namespace data_reduction_proxy {
39
40 // static
41 bool DataReductionProxyTamperDetection::DetectAndReport(
42 const net::HttpResponseHeaders* headers,
43 const bool scheme_is_https) {
44 DCHECK(headers);
45 // Abort tamper detection, if the fingerprint of the Chrome-Proxy header is
46 // absent.
47 std::string chrome_proxy_fingerprint;
48 if (!GetDataReductionProxyActionFingerprintChromeProxy(
49 headers, &chrome_proxy_fingerprint))
50 return false;
bengr 2014/08/04 16:54:21 add curly braces
xingx1 2014/08/04 18:17:11 Done.
51
52 // Get carrier ID.
53 unsigned carrier_id = 0;
54 #if defined(OS_ANDROID)
55 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &carrier_id);
56 #endif
57
58 DataReductionProxyTamperDetection tamper_detection(
59 headers,
bengr 2014/08/04 16:54:21 put on one line
xingx1 2014/08/04 18:17:11 Done.
60 scheme_is_https,
61 carrier_id);
62
63 // Checks if the Chrome-Proxy header has been tampered with.
64 if (tamper_detection.ValidateChromeProxyHeader(chrome_proxy_fingerprint)) {
65 tamper_detection.ReportUMAforChromeProxyHeaderValidation();
66 return true;
67 }
68
69 // Chrome-Proxy header has not been tampered with, and thus other
70 // fingerprints are valid. Reports the number of responses that other
71 // fingerprints will be checked.
72 REPORT_TAMPER_DETECTION_UMA(
73 scheme_is_https,
74 "DataReductionProxy.HTTPSHeaderTamperDetection",
75 "DataReductionProxy.HTTPHeaderTamperDetection",
76 carrier_id);
77
78 bool tampered = false;
79 std::string fingerprint;
80
81 if (GetDataReductionProxyActionFingerprintVia(headers, &fingerprint)) {
82 bool has_chrome_proxy_via_header;
83 if (tamper_detection.ValidateViaHeader(
84 fingerprint, &has_chrome_proxy_via_header)) {
85 tamper_detection.ReportUMAforViaHeaderValidation(
86 has_chrome_proxy_via_header);
87 tampered = true;
88 }
89 }
90
91 if (GetDataReductionProxyActionFingerprintOtherHeaders(
92 headers, &fingerprint)) {
93 if (tamper_detection.ValidateOtherHeaders(fingerprint)) {
94 tamper_detection.ReportUMAforOtherHeadersValidation();
95 tampered = true;
96 }
97 }
98
99 if (GetDataReductionProxyActionFingerprintContentLength(
100 headers, &fingerprint)) {
101 if (tamper_detection.ValidateContentLengthHeader(fingerprint)) {
102 tamper_detection.ReportUMAforContentLengthHeaderValidation();
103 tampered = true;
104 }
105 }
106
107 return tampered;
108 }
109
110 // Constructor initializes the map of fingerprint names to codes.
111 DataReductionProxyTamperDetection::DataReductionProxyTamperDetection(
112 const net::HttpResponseHeaders* headers,
113 const bool is_secure,
114 const unsigned carrier_id)
115 : response_headers_(headers),
116 scheme_is_https_(is_secure),
117 carrier_id_(carrier_id) {
118 DCHECK(headers);
119 };
120
121 DataReductionProxyTamperDetection::~DataReductionProxyTamperDetection() {};
122
123 // Checks whether the Chrome-Proxy header has been tampered with. |fingerprint|
124 // is the fingerprint received from the data reduction proxy, which is Base64
125 // encoded. Decodes it first. Then calculates the fingerprint of received
126 // Chrome-Proxy header, and compares the two to see whether they are equal or
127 // not.
128 bool DataReductionProxyTamperDetection::ValidateChromeProxyHeader(
129 const std::string& fingerprint) const {
130 std::string received_fingerprint;
131 if (!base::Base64Decode(fingerprint, &received_fingerprint))
132 return true;
133
134 // Gets the Chrome-Proxy header values with its fingerprint removed.
135 std::vector<std::string> chrome_proxy_header_values;
136 GetDataReductionProxyHeaderWithFingerprintRemoved(
137 response_headers_, &chrome_proxy_header_values);
138
139 // Calculates the MD5 hash value of Chrome-Proxy.
140 std::string actual_fingerprint;
141 GetMD5(ValuesToSortedString(&chrome_proxy_header_values),
142 &actual_fingerprint);
143
144 return received_fingerprint != actual_fingerprint;
145 }
146
147 void DataReductionProxyTamperDetection::
148 ReportUMAforChromeProxyHeaderValidation() const {
149 REPORT_TAMPER_DETECTION_UMA(
150 scheme_is_https_,
151 "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy",
152 "DataReductionProxy.HTTPHeaderTampered_ChromeProxy",
153 carrier_id_);
154 }
155
156 // Checks whether there are other proxies/middleboxes' named after the data
157 // reduction proxy's name in Via header. |has_chrome_proxy_via_header| marks
158 // that whether the data reduction proxy's Via header occurs or not.
159 bool DataReductionProxyTamperDetection::ValidateViaHeader(
160 const std::string& fingerprint, bool* has_chrome_proxy_via_header) const {
161 bool has_intermediary;
162 *has_chrome_proxy_via_header = HasDataReductionProxyViaHeader(
163 response_headers_,
164 &has_intermediary);
165
166 if (*has_chrome_proxy_via_header)
167 return !has_intermediary;
168 return false;
169 }
170
171 void DataReductionProxyTamperDetection::ReportUMAforViaHeaderValidation(
172 bool has_chrome_proxy) const {
173 // The Via header of the data reduction proxy is missing.
174 if (!has_chrome_proxy) {
175 REPORT_TAMPER_DETECTION_UMA(
176 scheme_is_https_,
177 "DataReductionProxy.HTTPSHeaderTampered_Via_Missing",
178 "DataReductionProxy.HTTPHeaderTampered_Via_Missing",
179 carrier_id_);
180 return;
181 }
182
183 REPORT_TAMPER_DETECTION_UMA(
184 scheme_is_https_,
185 "DataReductionProxy.HTTPSHeaderTampered_Via",
186 "DataReductionProxy.HTTPHeaderTampered_Via",
187 carrier_id_);
188 }
189
190 // Checks whether values of a predefined list of headers have been modified. At
191 // the data reduction proxy side, it constructs a canonical representation of
192 // values of a list of headers. The fingerprint is constructed as follows:
193 // 1) for each header, gets the string representation of its values (same as
194 // ValuesToSortedString);
195 // 2) concatenates all header's string representations using ";" as a delimiter;
196 // 3) calculates the MD5 hash value of above concatenated string;
197 // 4) appends the header names to the fingerprint, with a delimiter "|".
198 // The constructed fingerprint looks like:
199 // [hashed_fingerprint]|header_name1|header_namer2:...
200 //
201 // To check whether such a fingerprint matches the response that the Chromium
202 // client receives, the client firstly extracts the header names. For
203 // each header, gets its string representation (by ValuesToSortedString),
204 // concatenates them and calculates the MD5 hash value. Compares the hash
205 // value to the fingerprint received from the data reduction proxy.
206 bool DataReductionProxyTamperDetection::ValidateOtherHeaders(
207 const std::string& fingerprint) const {
208 std::string received_fingerprint;
209 DCHECK(!fingerprint.empty());
210
211 // "|" delimiter would not occur in base64 as well as header names.
bengr 2014/08/04 16:54:21 Say: According to RFC 2616, "|" is not a valid cha
xingx1 2014/08/04 18:17:11 Done.
212 net::HttpUtil::ValuesIterator it(
213 fingerprint.begin(), fingerprint.end(), '|');
214
215 // The first value is the base64 encoded fingerprint.
216 if (!(it.GetNext() &&
217 base::Base64Decode(it.value(), &received_fingerprint))) {
218 NOTREACHED();
219 return true;
220 }
221
222 std::string header_values;
223 // The following values are the header names included in the fingerprint
224 // calculation.
225 while (it.GetNext()) {
226 // Gets values of one header.
227 std::vector<std::string> response_header_values =
228 GetHeaderValues(response_headers_, it.value());
229 // Sorts the values and concatenate them, with delimiter ";". ";" would not
bengr 2014/08/04 16:54:21 Fix the commment because ';' can occur in header v
xingx1 2014/08/04 18:17:11 Done.
230 // occur in header values,
231 header_values += ValuesToSortedString(&response_header_values) + ";";
232 }
233
234 // Calculates the MD5 hash of the concatenated string.
235 std::string actual_fingerprint;
236 GetMD5(header_values, &actual_fingerprint);
237
238 return received_fingerprint != actual_fingerprint;
239 }
240
241 void DataReductionProxyTamperDetection::
242 ReportUMAforOtherHeadersValidation() const {
243 REPORT_TAMPER_DETECTION_UMA(
244 scheme_is_https_,
245 "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders",
246 "DataReductionProxy.HTTPHeaderTampered_OtherHeaders",
247 carrier_id_);
248 }
249
250 // Checks whether the Content-Length value is different from what the data
251 // reduction proxy sends. It will not be reported as different if at either
252 // side (the data reduction proxy side and the client side), the
253 // Content-Length is missing or it cannot be decoded as a valid integer.
254 bool DataReductionProxyTamperDetection::ValidateContentLengthHeader(
255 const std::string& fingerprint) const {
256 int received_content_length_fingerprint, actual_content_length;
257 // Abort, if Content-Length value from the data reduction proxy does not
258 // exist or it cannot be converted to an integer.
259 if (base::StringToInt(fingerprint, &received_content_length_fingerprint)) {
260 std::string actual_content_length_string;
261 // Abort, if there is no Content-Length header received.
262 if (response_headers_->GetNormalizedHeader("Content-Length",
263 &actual_content_length_string)) {
264 // Abort, if the Content-Length value cannot be converted to integer.
265 if (!base::StringToInt(actual_content_length_string,
266 &actual_content_length)) {
267 return false;
268 }
269
bengr 2014/08/04 16:54:21 Remove blank line.
xingx1 2014/08/04 18:17:11 Done.
270 return received_content_length_fingerprint != actual_content_length;
271 }
272 }
273 return false;
274 }
275
276 void DataReductionProxyTamperDetection::
277 ReportUMAforContentLengthHeaderValidation() const {
278 // Gets MIME type of the response and reports to UMA histograms separately.
279 // Divides MIME types into 4 groups: JavaScript, CSS, Images, and others.
280 REPORT_TAMPER_DETECTION_UMA(
281 scheme_is_https_,
282 "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
283 "DataReductionProxy.HTTPHeaderTampered_ContentLength",
284 carrier_id_);
285
286 // Gets MIME type.
287 std::string mime_type;
288 response_headers_->GetMimeType(&mime_type);
289
290 // Reports tampered JavaScript.
291 if (mime_type.compare("text/javascript") == 0 ||
292 mime_type.compare("application/x-javascript") == 0 ||
293 mime_type.compare("application/javascript") == 0) {
294 REPORT_TAMPER_DETECTION_UMA(
295 scheme_is_https_,
296 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS",
297 "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS",
298 carrier_id_);
299 }
300 // Reports tampered CSSs.
301 else if (mime_type.compare("text/css") == 0) {
302 REPORT_TAMPER_DETECTION_UMA(
303 scheme_is_https_,
304 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS",
305 "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS",
306 carrier_id_);
307 }
308 // Reports tampered images.
309 else if (mime_type.find("image/") == 0) {
310 REPORT_TAMPER_DETECTION_UMA(
311 scheme_is_https_,
312 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image",
313 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image",
314 carrier_id_);
315 }
316 // Reports tampered other MIME types.
317 else {
318 REPORT_TAMPER_DETECTION_UMA(
319 scheme_is_https_,
320 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other",
321 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other",
322 carrier_id_);
323 }
324 }
325
326 // We construct a canonical representation of the header so that reordered
327 // header values will produce the same fingerprint. The fingerprint is
328 // constructed as follows:
329 // 1) sort the values;
330 // 2) concatenate sorted values with a "," delimiter.
331 std::string DataReductionProxyTamperDetection::ValuesToSortedString(
332 std::vector<std::string>* values) {
333 std::string concatenated_values;
334 DCHECK(values);
335 if (!values) return "";
336
337 std::sort(values->begin(), values->end());
338 for (size_t i = 0; i < values->size(); ++i) {
339 // Concatenates with delimiter ",".
340 concatenated_values += (*values)[i] + ",";
341 }
342 return concatenated_values;
343 }
344
345 void DataReductionProxyTamperDetection::GetMD5(
346 const std::string& input, std::string* output) {
347 base::MD5Digest digest;
348 base::MD5Sum(input.c_str(), input.size(), &digest);
349 *output = std::string((char*)digest.a, ARRAYSIZE_UNSAFE(digest.a));
350 }
351
352 std::vector<std::string> DataReductionProxyTamperDetection::GetHeaderValues(
353 const net::HttpResponseHeaders* headers, const std::string& header_name) {
354 std::vector<std::string> values;
355 std::string value;
356 void* iter = NULL;
357 while (headers->EnumerateHeader(&iter, header_name, &value)) {
358 values.push_back(value);
359 }
360 return values;
361 }
362
363 } // namespace data_reduction_proxy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698