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

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

Powered by Google App Engine
This is Rietveld 408576698