ProjectingObserverChromeos: Drop DBusThreadManager dependency for better testing.
[chromium-blink-merge.git] / components / data_reduction_proxy / browser / data_reduction_proxy_tamper_detection.cc
blob3cb822ab4ceb88a2e23041b06f329dd676abb78b
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.
5 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.h"
7 #include <algorithm>
8 #include <cstring>
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 "base/strings/string_util.h"
16 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
17 #include "net/http/http_response_headers.h"
18 #include "net/http/http_util.h"
20 #if defined(OS_ANDROID)
21 #include "net/android/network_library.h"
22 #endif
24 // Macro for UMA reporting. HTTP response first reports to histogram events
25 // |http_histogram| by |carrier_id|; then reports the total counts to
26 // |http_histogram|_Total. HTTPS response reports to histograms
27 // |https_histogram| and |https_histogram|_Total similarly.
28 #define REPORT_TAMPER_DETECTION_UMA( \
29 scheme_is_https, https_histogram, http_histogram, carrier_id) \
30 do { \
31 if (scheme_is_https) { \
32 UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, carrier_id); \
33 UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
34 } else { \
35 UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, carrier_id); \
36 UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
38 } while (0)
40 namespace data_reduction_proxy {
42 // static
43 bool DataReductionProxyTamperDetection::DetectAndReport(
44 const net::HttpResponseHeaders* headers,
45 const bool scheme_is_https) {
46 DCHECK(headers);
47 // Abort tamper detection, if the fingerprint of the Chrome-Proxy header is
48 // absent.
49 std::string chrome_proxy_fingerprint;
50 if (!GetDataReductionProxyActionFingerprintChromeProxy(
51 headers, &chrome_proxy_fingerprint)) {
52 return false;
55 // Get carrier ID.
56 unsigned carrier_id = 0;
57 #if defined(OS_ANDROID)
58 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &carrier_id);
59 #endif
61 DataReductionProxyTamperDetection tamper_detection(
62 headers, scheme_is_https, carrier_id);
64 // Checks if the Chrome-Proxy header has been tampered with.
65 if (tamper_detection.ValidateChromeProxyHeader(chrome_proxy_fingerprint)) {
66 tamper_detection.ReportUMAforChromeProxyHeaderValidation();
67 return true;
70 // Chrome-Proxy header has not been tampered with, and thus other
71 // fingerprints are valid. Reports the number of responses that other
72 // fingerprints will be checked.
73 REPORT_TAMPER_DETECTION_UMA(
74 scheme_is_https,
75 "DataReductionProxy.HeaderTamperDetectionHTTPS",
76 "DataReductionProxy.HeaderTamperDetectionHTTP",
77 carrier_id);
79 bool tampered = false;
80 std::string fingerprint;
82 if (GetDataReductionProxyActionFingerprintVia(headers, &fingerprint)) {
83 bool has_chrome_proxy_via_header;
84 if (tamper_detection.ValidateViaHeader(
85 fingerprint, &has_chrome_proxy_via_header)) {
86 tamper_detection.ReportUMAforViaHeaderValidation(
87 has_chrome_proxy_via_header);
88 tampered = true;
92 if (GetDataReductionProxyActionFingerprintOtherHeaders(
93 headers, &fingerprint)) {
94 if (tamper_detection.ValidateOtherHeaders(fingerprint)) {
95 tamper_detection.ReportUMAforOtherHeadersValidation();
96 tampered = true;
100 if (GetDataReductionProxyActionFingerprintContentLength(
101 headers, &fingerprint)) {
102 if (tamper_detection.ValidateContentLengthHeader(fingerprint)) {
103 tamper_detection.ReportUMAforContentLengthHeaderValidation();
104 tampered = true;
108 if (!tampered) {
109 REPORT_TAMPER_DETECTION_UMA(
110 scheme_is_https,
111 "DataReductionProxy.HeaderTamperDetectionPassHTTPS",
112 "DataReductionProxy.HeaderTamperDetectionPassHTTP",
113 carrier_id);
116 return tampered;
119 // Constructor initializes the map of fingerprint names to codes.
120 DataReductionProxyTamperDetection::DataReductionProxyTamperDetection(
121 const net::HttpResponseHeaders* headers,
122 const bool is_secure,
123 const unsigned carrier_id)
124 : response_headers_(headers),
125 scheme_is_https_(is_secure),
126 carrier_id_(carrier_id) {
127 DCHECK(headers);
130 DataReductionProxyTamperDetection::~DataReductionProxyTamperDetection() {};
132 // |fingerprint| is Base64 encoded. Decodes it first. Then calculates the
133 // fingerprint of received Chrome-Proxy header, and compares the two to see
134 // whether they are equal or not.
135 bool DataReductionProxyTamperDetection::ValidateChromeProxyHeader(
136 const std::string& fingerprint) const {
137 std::string received_fingerprint;
138 if (!base::Base64Decode(fingerprint, &received_fingerprint))
139 return true;
141 // Gets the Chrome-Proxy header values with its fingerprint removed.
142 std::vector<std::string> chrome_proxy_header_values;
143 GetDataReductionProxyHeaderWithFingerprintRemoved(
144 response_headers_, &chrome_proxy_header_values);
146 // Calculates the MD5 hash value of Chrome-Proxy.
147 std::string actual_fingerprint;
148 GetMD5(ValuesToSortedString(&chrome_proxy_header_values),
149 &actual_fingerprint);
151 return received_fingerprint != actual_fingerprint;
154 void DataReductionProxyTamperDetection::
155 ReportUMAforChromeProxyHeaderValidation() const {
156 REPORT_TAMPER_DETECTION_UMA(
157 scheme_is_https_,
158 "DataReductionProxy.HeaderTamperedHTTPS_ChromeProxy",
159 "DataReductionProxy.HeaderTamperedHTTP_ChromeProxy",
160 carrier_id_);
163 // Checks whether there are other proxies/middleboxes' named after the data
164 // reduction proxy's name in Via header. |has_chrome_proxy_via_header| marks
165 // that whether the data reduction proxy's Via header occurs or not.
166 bool DataReductionProxyTamperDetection::ValidateViaHeader(
167 const std::string& fingerprint,
168 bool* has_chrome_proxy_via_header) const {
169 bool has_intermediary;
170 *has_chrome_proxy_via_header = HasDataReductionProxyViaHeader(
171 response_headers_,
172 &has_intermediary);
174 if (*has_chrome_proxy_via_header)
175 return !has_intermediary;
176 return true;
179 void DataReductionProxyTamperDetection::ReportUMAforViaHeaderValidation(
180 bool has_chrome_proxy) const {
181 // The Via header of the data reduction proxy is missing.
182 if (!has_chrome_proxy) {
183 REPORT_TAMPER_DETECTION_UMA(
184 scheme_is_https_,
185 "DataReductionProxy.HeaderTamperedHTTPS_Via_Missing",
186 "DataReductionProxy.HeaderTamperedHTTP_Via_Missing",
187 carrier_id_);
188 return;
191 REPORT_TAMPER_DETECTION_UMA(
192 scheme_is_https_,
193 "DataReductionProxy.HeaderTamperedHTTPS_Via",
194 "DataReductionProxy.HeaderTamperedHTTP_Via",
195 carrier_id_);
198 // The data reduction proxy constructs a canonical representation of values of
199 // a list of headers. The fingerprint is constructed as follows:
200 // 1) for each header, gets the string representation of its values (same as
201 // ValuesToSortedString);
202 // 2) concatenates all header's string representations using ";" as a delimiter;
203 // 3) calculates the MD5 hash value of above concatenated string;
204 // 4) appends the header names to the fingerprint, with a delimiter "|".
205 // The constructed fingerprint looks like:
206 // [hashed_fingerprint]|header_name1|header_namer2:...
208 // To check whether such a fingerprint matches the response that the Chromium
209 // client receives, the client firstly extracts the header names. For
210 // each header, gets its string representation (by ValuesToSortedString),
211 // concatenates them and calculates the MD5 hash value. Compares the hash
212 // value to the fingerprint received from the data reduction proxy.
213 bool DataReductionProxyTamperDetection::ValidateOtherHeaders(
214 const std::string& fingerprint) const {
215 DCHECK(!fingerprint.empty());
217 // According to RFC 2616, "|" is not a valid character in a header name; and
218 // it is not a valid base64 encoding character, so there is no ambituity in
219 //using it as a delimiter.
220 net::HttpUtil::ValuesIterator it(
221 fingerprint.begin(), fingerprint.end(), '|');
223 // The first value is the base64 encoded fingerprint.
224 std::string received_fingerprint;
225 if (!it.GetNext() ||
226 !base::Base64Decode(it.value(), &received_fingerprint)) {
227 NOTREACHED();
228 return true;
231 std::string header_values;
232 // The following values are the header names included in the fingerprint
233 // calculation.
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 ";". ";" can occur
239 // in a header value and thus two different sets of header values could map
240 // to the same string representation. This should be very rare.
241 // TODO(xingx): find an unambiguous representation.
242 header_values += ValuesToSortedString(&response_header_values) + ";";
245 // Calculates the MD5 hash of the concatenated string.
246 std::string actual_fingerprint;
247 GetMD5(header_values, &actual_fingerprint);
249 return received_fingerprint != actual_fingerprint;
252 void DataReductionProxyTamperDetection::
253 ReportUMAforOtherHeadersValidation() const {
254 REPORT_TAMPER_DETECTION_UMA(
255 scheme_is_https_,
256 "DataReductionProxy.HeaderTamperedHTTPS_OtherHeaders",
257 "DataReductionProxy.HeaderTamperedHTTP_OtherHeaders",
258 carrier_id_);
261 // The Content-Length value will not be reported as different if at either side
262 // (the data reduction proxy side and the client side), the Content-Length is
263 // missing or it cannot be decoded as a valid integer.
264 bool DataReductionProxyTamperDetection::ValidateContentLengthHeader(
265 const std::string& fingerprint) const {
266 int received_content_length_fingerprint, actual_content_length;
267 // Abort, if Content-Length value from the data reduction proxy does not
268 // exist or it cannot be converted to an integer.
269 if (!base::StringToInt(fingerprint, &received_content_length_fingerprint))
270 return false;
272 std::string actual_content_length_string;
273 // Abort, if there is no Content-Length header received.
274 if (!response_headers_->GetNormalizedHeader("Content-Length",
275 &actual_content_length_string)) {
276 return false;
279 // Abort, if the Content-Length value cannot be converted to integer.
280 if (!base::StringToInt(actual_content_length_string,
281 &actual_content_length)) {
282 return false;
285 return received_content_length_fingerprint != actual_content_length;
288 void DataReductionProxyTamperDetection::
289 ReportUMAforContentLengthHeaderValidation() const {
290 // Gets MIME type of the response and reports to UMA histograms separately.
291 // Divides MIME types into 4 groups: JavaScript, CSS, Images, and others.
292 REPORT_TAMPER_DETECTION_UMA(
293 scheme_is_https_,
294 "DataReductionProxy.HeaderTamperedHTTPS_ContentLength",
295 "DataReductionProxy.HeaderTamperedHTTP_ContentLength",
296 carrier_id_);
298 // Gets MIME type.
299 std::string mime_type;
300 response_headers_->GetMimeType(&mime_type);
302 std::string JS1 = "text/javascript";
303 std::string JS2 = "application/x-javascript";
304 std::string JS3 = "application/javascript";
305 std::string CSS = "text/css";
306 std::string IMAGE = "image/";
308 size_t mime_type_size = mime_type.size();
309 if ((mime_type_size >= JS1.size() && LowerCaseEqualsASCII(mime_type.begin(),
310 mime_type.begin() + JS1.size(), JS1.c_str())) ||
311 (mime_type_size >= JS2.size() && LowerCaseEqualsASCII(mime_type.begin(),
312 mime_type.begin() + JS2.size(), JS2.c_str())) ||
313 (mime_type_size >= JS3.size() && LowerCaseEqualsASCII(mime_type.begin(),
314 mime_type.begin() + JS3.size(), JS3.c_str()))) {
315 REPORT_TAMPER_DETECTION_UMA(
316 scheme_is_https_,
317 "DataReductionProxy.HeaderTamperedHTTPS_ContentLength_JS",
318 "DataReductionProxy.HeaderTamperedHTTP_ContentLength_JS",
319 carrier_id_);
320 } else if (mime_type_size >= CSS.size() &&
321 LowerCaseEqualsASCII(mime_type.begin(),
322 mime_type.begin() + CSS.size(), CSS.c_str())) {
323 REPORT_TAMPER_DETECTION_UMA(
324 scheme_is_https_,
325 "DataReductionProxy.HeaderTamperedHTTPS_ContentLength_CSS",
326 "DataReductionProxy.HeaderTamperedHTTP_ContentLength_CSS",
327 carrier_id_);
328 } else if (mime_type_size >= IMAGE.size() &&
329 LowerCaseEqualsASCII(mime_type.begin(),
330 mime_type.begin() + IMAGE.size(), IMAGE.c_str())) {
331 REPORT_TAMPER_DETECTION_UMA(
332 scheme_is_https_,
333 "DataReductionProxy.HeaderTamperedHTTPS_ContentLength_Image",
334 "DataReductionProxy.HeaderTamperedHTTP_ContentLength_Image",
335 carrier_id_);
336 } else {
337 REPORT_TAMPER_DETECTION_UMA(
338 scheme_is_https_,
339 "DataReductionProxy.HeaderTamperedHTTPS_ContentLength_Other",
340 "DataReductionProxy.HeaderTamperedHTTP_ContentLength_Other",
341 carrier_id_);
345 // We construct a canonical representation of the header so that reordered
346 // header values will produce the same fingerprint. The fingerprint is
347 // constructed as follows:
348 // 1) sort the values;
349 // 2) concatenate sorted values with a "," delimiter.
350 std::string DataReductionProxyTamperDetection::ValuesToSortedString(
351 std::vector<std::string>* values) {
352 std::string concatenated_values;
353 DCHECK(values);
354 if (!values) return "";
356 std::sort(values->begin(), values->end());
357 for (size_t i = 0; i < values->size(); ++i) {
358 // Concatenates with delimiter ",".
359 concatenated_values += (*values)[i] + ",";
361 return concatenated_values;
364 void DataReductionProxyTamperDetection::GetMD5(
365 const std::string& input, std::string* output) {
366 base::MD5Digest digest;
367 base::MD5Sum(input.c_str(), input.size(), &digest);
368 *output = std::string(
369 reinterpret_cast<char*>(digest.a), ARRAYSIZE_UNSAFE(digest.a));
372 std::vector<std::string> DataReductionProxyTamperDetection::GetHeaderValues(
373 const net::HttpResponseHeaders* headers,
374 const std::string& header_name) {
375 std::vector<std::string> values;
376 std::string value;
377 void* iter = NULL;
378 while (headers->EnumerateHeader(&iter, header_name, &value)) {
379 values.push_back(value);
381 return values;
384 } // namespace data_reduction_proxy