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

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( \
28 scheme_is_https, http_histogram, https_histogram, carrier_id) \
29 do { \
30 if (scheme_is_https) { \
31 UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, carrier_id); \
32 UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
33 } else { \
34 UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, carrier_id); \
35 UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
36 }\
37 } while (0)
38
39 namespace data_reduction_proxy {
40
41 // static
42 bool DataReductionProxyTamperDetection::DetectAndReport(
43 const net::HttpResponseHeaders* headers,
44 const bool scheme_is_https) {
45 DCHECK(headers);
46 // Abort tamper detection, if the fingerprint of the Chrome-Proxy header is
47 // absent.
48 std::string chrome_proxy_fingerprint;
49 if (!GetDataReductionProxyActionFingerprintChromeProxy(
50 headers, &chrome_proxy_fingerprint)) {
51 return false;
52 }
53
54 // Get carrier ID.
55 unsigned carrier_id = 0;
56 #if defined(OS_ANDROID)
57 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &carrier_id);
58 #endif
59
60 DataReductionProxyTamperDetection tamper_detection(
61 headers, scheme_is_https, 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.HeaderTamperDetectionHTTPS",
75 "DataReductionProxy.HeaderTamperDetectionHTTP",
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 // |fingerprint| is Base64 encoded. Decodes it first. Then calculates the
124 // fingerprint of received Chrome-Proxy header, and compares the two to see
125 // whether they are equal or not.
126 bool DataReductionProxyTamperDetection::ValidateChromeProxyHeader(
127 const std::string& fingerprint) const {
128 std::string received_fingerprint;
129 if (!base::Base64Decode(fingerprint, &received_fingerprint))
130 return true;
131
132 // Gets the Chrome-Proxy header values with its fingerprint removed.
133 std::vector<std::string> chrome_proxy_header_values;
134 GetDataReductionProxyHeaderWithFingerprintRemoved(
135 response_headers_, &chrome_proxy_header_values);
136
137 // Calculates the MD5 hash value of Chrome-Proxy.
138 std::string actual_fingerprint;
139 GetMD5(ValuesToSortedString(&chrome_proxy_header_values),
140 &actual_fingerprint);
141
142 return received_fingerprint != actual_fingerprint;
143 }
144
145 void DataReductionProxyTamperDetection::
146 ReportUMAforChromeProxyHeaderValidation() const {
147 REPORT_TAMPER_DETECTION_UMA(
148 scheme_is_https_,
149 "DataReductionProxy.HeaderTamperedHTTPS_ChromeProxy",
150 "DataReductionProxy.HeaderTamperedHTTP_ChromeProxy",
151 carrier_id_);
152 }
153
154 // Checks whether there are other proxies/middleboxes' named after the data
155 // reduction proxy's name in Via header. |has_chrome_proxy_via_header| marks
156 // that whether the data reduction proxy's Via header occurs or not.
157 bool DataReductionProxyTamperDetection::ValidateViaHeader(
158 const std::string& fingerprint,
159 bool* has_chrome_proxy_via_header) const {
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.HeaderTamperedHTTPS_Via_Missing",
177 "DataReductionProxy.HeaderTamperedHTTP_Via_Missing",
178 carrier_id_);
179 return;
180 }
181
182 REPORT_TAMPER_DETECTION_UMA(
183 scheme_is_https_,
184 "DataReductionProxy.HeaderTamperedHTTPS_Via",
185 "DataReductionProxy.HeaderTamperedHTTP_Via",
186 carrier_id_);
187 }
188
189 // The data reduction proxy constructs a canonical representation of values of
190 // a list of headers. The fingerprint is constructed as follows:
191 // 1) for each header, gets the string representation of its values (same as
192 // ValuesToSortedString);
193 // 2) concatenates all header's string representations using ";" as a delimiter;
194 // 3) calculates the MD5 hash value of above concatenated string;
195 // 4) appends the header names to the fingerprint, with a delimiter "|".
196 // The constructed fingerprint looks like:
197 // [hashed_fingerprint]|header_name1|header_namer2:...
198 //
199 // To check whether such a fingerprint matches the response that the Chromium
200 // client receives, the client firstly extracts the header names. For
201 // each header, gets its string representation (by ValuesToSortedString),
202 // concatenates them and calculates the MD5 hash value. Compares the hash
203 // value to the fingerprint received from the data reduction proxy.
204 bool DataReductionProxyTamperDetection::ValidateOtherHeaders(
205 const std::string& fingerprint) const {
206 DCHECK(!fingerprint.empty());
207
208 // According to RFC 2616, "|" is not a valid character in a header name; and
209 // it is not a valid base64 encoding character, so there is no ambituity in
210 //using it as a delimiter.
211 net::HttpUtil::ValuesIterator it(
212 fingerprint.begin(), fingerprint.end(), '|');
213
214 // The first value is the base64 encoded fingerprint.
215 std::string received_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 ";". ";" can occur
230 // in a header value and thus two different sets of header values could map
231 // to the same string representation. This should be very rare.
232 // TODO(xingx): find an unambiguous representation.
233 header_values += ValuesToSortedString(&response_header_values) + ";";
234 }
235
236 // Calculates the MD5 hash of the concatenated string.
237 std::string actual_fingerprint;
238 GetMD5(header_values, &actual_fingerprint);
239
240 return received_fingerprint != actual_fingerprint;
241 }
242
243 void DataReductionProxyTamperDetection::
244 ReportUMAforOtherHeadersValidation() const {
245 REPORT_TAMPER_DETECTION_UMA(
246 scheme_is_https_,
247 "DataReductionProxy.HeaderTamperedHTTPS_OtherHeaders",
248 "DataReductionProxy.HeaderTamperedHTTP_OtherHeaders",
249 carrier_id_);
250 }
251
252 // The Content-Length value will not be reported as different if at either side
253 // (the data reduction proxy side and the client side), the Content-Length is
254 // missing or it cannot be decoded as a valid integer.
255 bool DataReductionProxyTamperDetection::ValidateContentLengthHeader(
256 const std::string& fingerprint) const {
257 int received_content_length_fingerprint, actual_content_length;
258 // Abort, if Content-Length value from the data reduction proxy does not
259 // exist or it cannot be converted to an integer.
260 if (!base::StringToInt(fingerprint, &received_content_length_fingerprint))
261 return false;
262
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 return false;
268 }
269
270 // Abort, if the Content-Length value cannot be converted to integer.
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 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.HeaderTamperedHTTPS_ContentLength",
286 "DataReductionProxy.HeaderTamperedHTTP_ContentLength",
287 carrier_id_);
288
289 // Gets MIME type.
290 std::string mime_type;
291 response_headers_->GetMimeType(&mime_type);
292
293 if (mime_type.compare("text/javascript") == 0 ||
294 mime_type.compare("application/x-javascript") == 0 ||
295 mime_type.compare("application/javascript") == 0) {
296 REPORT_TAMPER_DETECTION_UMA(
297 scheme_is_https_,
298 "DataReductionProxy.HeaderTamperedHTTPS_ContentLength_JS",
299 "DataReductionProxy.HeaderTamperedHTTP_ContentLength_JS",
300 carrier_id_);
301 } else if (mime_type.compare("text/css") == 0) {
302 REPORT_TAMPER_DETECTION_UMA(
303 scheme_is_https_,
304 "DataReductionProxy.HeaderTamperedHTTPS_ContentLength_CSS",
305 "DataReductionProxy.HeaderTamperedHTTP_ContentLength_CSS",
306 carrier_id_);
307 } else if (mime_type.find("image/") == 0) {
308 REPORT_TAMPER_DETECTION_UMA(
309 scheme_is_https_,
310 "DataReductionProxy.HeaderTamperedHTTPS_ContentLength_Image",
311 "DataReductionProxy.HeaderTamperedHTTP_ContentLength_Image",
312 carrier_id_);
313 } else {
314 REPORT_TAMPER_DETECTION_UMA(
315 scheme_is_https_,
316 "DataReductionProxy.HeaderTamperedHTTPS_ContentLength_Other",
317 "DataReductionProxy.HeaderTamperedHTTP_ContentLength_Other",
318 carrier_id_);
319 }
320 }
321
322 // We construct a canonical representation of the header so that reordered
323 // header values will produce the same fingerprint. The fingerprint is
324 // constructed as follows:
325 // 1) sort the values;
326 // 2) concatenate sorted values with a "," delimiter.
327 std::string DataReductionProxyTamperDetection::ValuesToSortedString(
328 std::vector<std::string>* values) {
329 std::string concatenated_values;
330 DCHECK(values);
331 if (!values) return "";
332
333 std::sort(values->begin(), values->end());
334 for (size_t i = 0; i < values->size(); ++i) {
335 // Concatenates with delimiter ",".
336 concatenated_values += (*values)[i] + ",";
337 }
338 return concatenated_values;
339 }
340
341 void DataReductionProxyTamperDetection::GetMD5(
342 const std::string& input, std::string* output) {
343 base::MD5Digest digest;
344 base::MD5Sum(input.c_str(), input.size(), &digest);
345 *output = std::string(
346 reinterpret_cast<char*>(digest.a), ARRAYSIZE_UNSAFE(digest.a));
347 }
348
349 std::vector<std::string> DataReductionProxyTamperDetection::GetHeaderValues(
350 const net::HttpResponseHeaders* headers,
351 const std::string& header_name) {
352 std::vector<std::string> values;
353 std::string value;
354 void* iter = NULL;
355 while (headers->EnumerateHeader(&iter, header_name, &value)) {
356 values.push_back(value);
357 }
358 return values;
359 }
360
361 } // namespace data_reduction_proxy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698