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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.cc
diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.cc b/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0580a5daf332078353adab256f262d9b4d143b1d
--- /dev/null
+++ b/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.cc
@@ -0,0 +1,366 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include "base/base64.h"
+#include "base/md5.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+#if defined(OS_ANDROID)
+#include "net/android/network_library.h"
+#endif
+
+// Macro for UMA reporting. HTTP response first reports to histogram events
+// |http_histogram| by |carrier_id|; then reports the total counts to
+// |http_histogram|_Total. HTTPS response reports to histograms
+// |https_histogram| and |https_histogram|_Total similarly.
+#define REPORT_TAMPER_DETECTION_UMA(scheme_is_https, http_histogram, https_histogram, 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.
+ do { \
+ if (scheme_is_https) { \
+ UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, carrier_id); \
+ UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
+ } else { \
+ UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, carrier_id); \
+ UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
+ }\
+ } while (0)
+
+namespace data_reduction_proxy {
+
+// static
+bool DataReductionProxyTamperDetection::DetectAndReport(
+ const net::HttpResponseHeaders* headers,
+ const bool scheme_is_https) {
+ DCHECK(headers);
+ // Abort tamper detection, if the fingerprint of the Chrome-Proxy header is
+ // absent.
+ std::string chrome_proxy_fingerprint;
+ if (!GetDataReductionProxyActionFingerprintChromeProxy(
+ headers, &chrome_proxy_fingerprint)) {
+ return false;
+ }
+
+ // Get carrier ID.
+ unsigned carrier_id = 0;
+#if defined(OS_ANDROID)
+ base::StringToUint(net::android::GetTelephonyNetworkOperator(), &carrier_id);
+#endif
+
+ DataReductionProxyTamperDetection tamper_detection(
+ headers, scheme_is_https, carrier_id);
+
+ // Checks if the Chrome-Proxy header has been tampered with.
+ if (tamper_detection.ValidateChromeProxyHeader(chrome_proxy_fingerprint)) {
+ tamper_detection.ReportUMAforChromeProxyHeaderValidation();
+ return true;
+ }
+
+ // Chrome-Proxy header has not been tampered with, and thus other
+ // fingerprints are valid. Reports the number of responses that other
+ // fingerprints will be checked.
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https,
+ "DataReductionProxy.HTTPSHeaderTamperDetection",
+ "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.
+ carrier_id);
+
+ bool tampered = false;
+ std::string fingerprint;
+
+ if (GetDataReductionProxyActionFingerprintVia(headers, &fingerprint)) {
+ bool has_chrome_proxy_via_header;
+ if (tamper_detection.ValidateViaHeader(
+ fingerprint, &has_chrome_proxy_via_header)) {
+ tamper_detection.ReportUMAforViaHeaderValidation(
+ has_chrome_proxy_via_header);
+ tampered = true;
+ }
+ }
+
+ if (GetDataReductionProxyActionFingerprintOtherHeaders(
+ headers, &fingerprint)) {
+ if (tamper_detection.ValidateOtherHeaders(fingerprint)) {
+ tamper_detection.ReportUMAforOtherHeadersValidation();
+ tampered = true;
+ }
+ }
+
+ if (GetDataReductionProxyActionFingerprintContentLength(
+ headers, &fingerprint)) {
+ if (tamper_detection.ValidateContentLengthHeader(fingerprint)) {
+ tamper_detection.ReportUMAforContentLengthHeaderValidation();
+ tampered = true;
+ }
+ }
+
+ return tampered;
+}
+
+// Constructor initializes the map of fingerprint names to codes.
+DataReductionProxyTamperDetection::DataReductionProxyTamperDetection(
+ const net::HttpResponseHeaders* headers,
+ const bool is_secure,
+ const unsigned carrier_id)
+ : response_headers_(headers),
+ scheme_is_https_(is_secure),
+ carrier_id_(carrier_id) {
+ 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
+};
Alexei Svitkine (slow) 2014/08/04 21:07:51 Nit: No ; needed
xingx1 2014/08/04 23:54:21 Done.
+
+DataReductionProxyTamperDetection::~DataReductionProxyTamperDetection() {};
+
+// Checks whether the Chrome-Proxy header has been tampered with. |fingerprint|
+// is the fingerprint received from the data reduction proxy, which is Base64
+// encoded. Decodes it first. Then calculates the fingerprint of received
+// Chrome-Proxy header, and compares the two to see whether they are equal or
+// 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
+bool DataReductionProxyTamperDetection::ValidateChromeProxyHeader(
+ const std::string& fingerprint) const {
+ std::string received_fingerprint;
+ if (!base::Base64Decode(fingerprint, &received_fingerprint))
+ return true;
+
+ // Gets the Chrome-Proxy header values with its fingerprint removed.
+ std::vector<std::string> chrome_proxy_header_values;
+ GetDataReductionProxyHeaderWithFingerprintRemoved(
+ response_headers_, &chrome_proxy_header_values);
+
+ // Calculates the MD5 hash value of Chrome-Proxy.
+ std::string actual_fingerprint;
+ GetMD5(ValuesToSortedString(&chrome_proxy_header_values),
+ &actual_fingerprint);
+
+ return received_fingerprint != actual_fingerprint;
+}
+
+void DataReductionProxyTamperDetection::
+ ReportUMAforChromeProxyHeaderValidation() const {
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https_,
+ "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy",
+ "DataReductionProxy.HTTPHeaderTampered_ChromeProxy",
+ carrier_id_);
+}
+
+// Checks whether there are other proxies/middleboxes' named after the data
+// reduction proxy's name in Via header. |has_chrome_proxy_via_header| marks
+// that whether the data reduction proxy's Via header occurs or not.
+bool DataReductionProxyTamperDetection::ValidateViaHeader(
+ 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.
+ bool has_intermediary;
+ *has_chrome_proxy_via_header = HasDataReductionProxyViaHeader(
+ response_headers_,
+ &has_intermediary);
+
+ if (*has_chrome_proxy_via_header)
+ return !has_intermediary;
+ return false;
+}
+
+void DataReductionProxyTamperDetection::ReportUMAforViaHeaderValidation(
+ bool has_chrome_proxy) const {
+ // The Via header of the data reduction proxy is missing.
+ if (!has_chrome_proxy) {
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https_,
+ "DataReductionProxy.HTTPSHeaderTampered_Via_Missing",
+ "DataReductionProxy.HTTPHeaderTampered_Via_Missing",
+ carrier_id_);
+ return;
+ }
+
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https_,
+ "DataReductionProxy.HTTPSHeaderTampered_Via",
+ "DataReductionProxy.HTTPHeaderTampered_Via",
+ carrier_id_);
+}
+
+// Checks whether values of a predefined list of headers have been modified. At
+// the data reduction proxy side, it constructs a canonical representation of
+// values of a list of headers. The fingerprint is constructed as follows:
+// 1) for each header, gets the string representation of its values (same as
+// ValuesToSortedString);
+// 2) concatenates all header's string representations using ";" as a delimiter;
+// 3) calculates the MD5 hash value of above concatenated string;
+// 4) appends the header names to the fingerprint, with a delimiter "|".
+// The constructed fingerprint looks like:
+// [hashed_fingerprint]|header_name1|header_namer2:...
+//
+// To check whether such a fingerprint matches the response that the Chromium
+// client receives, the client firstly extracts the header names. For
+// each header, gets its string representation (by ValuesToSortedString),
+// concatenates them and calculates the MD5 hash value. Compares the hash
+// value to the fingerprint received from the data reduction proxy.
+bool DataReductionProxyTamperDetection::ValidateOtherHeaders(
+ const std::string& fingerprint) const {
+ 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.
+ DCHECK(!fingerprint.empty());
+
+ // According to RFC 2616, "|" is not a valid character in a header name; and
+ // it is not a valid base64 encoding character, so there is no ambituity in
+ //using it as a delimiter.
+ net::HttpUtil::ValuesIterator it(
+ fingerprint.begin(), fingerprint.end(), '|');
+
+ // The first value is the base64 encoded fingerprint.
+ if (!(it.GetNext() &&
+ 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.
+ NOTREACHED();
+ return true;
+ }
+
+ std::string header_values;
+ // The following values are the header names included in the fingerprint
+ // calculation.
+ while (it.GetNext()) {
+ // Gets values of one header.
+ std::vector<std::string> response_header_values =
+ GetHeaderValues(response_headers_, it.value());
+ // Sorts the values and concatenate them, with delimiter ";". ";" can occur
+ // in a header value and thus two different sets of header values could map
+ // to the same string representation. This should be very rare.
+ // TODO(xingx): find an unambiguous representation.
+ header_values += ValuesToSortedString(&response_header_values) + ";";
+ }
+
+ // Calculates the MD5 hash of the concatenated string.
+ std::string actual_fingerprint;
+ GetMD5(header_values, &actual_fingerprint);
+
+ return received_fingerprint != actual_fingerprint;
+}
+
+void DataReductionProxyTamperDetection::
+ ReportUMAforOtherHeadersValidation() const {
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https_,
+ "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders",
+ "DataReductionProxy.HTTPHeaderTampered_OtherHeaders",
+ carrier_id_);
+}
+
+// Checks whether the Content-Length value is different from what the data
+// reduction proxy sends. It will not be reported as different if at either
+// side (the data reduction proxy side and the client side), the
+// Content-Length is missing or it cannot be decoded as a valid integer.
+bool DataReductionProxyTamperDetection::ValidateContentLengthHeader(
+ const std::string& fingerprint) const {
+ int received_content_length_fingerprint, actual_content_length;
+ // Abort, if Content-Length value from the data reduction proxy does not
+ // exist or it cannot be converted to an integer.
+ 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.
+ std::string actual_content_length_string;
+ // Abort, if there is no Content-Length header received.
+ if (response_headers_->GetNormalizedHeader("Content-Length",
+ &actual_content_length_string)) {
+ // Abort, if the Content-Length value cannot be converted to integer.
+ if (!base::StringToInt(actual_content_length_string,
+ &actual_content_length)) {
+ return false;
+ }
+
+ return received_content_length_fingerprint != actual_content_length;
+ }
+ }
+ return false;
+}
+
+void DataReductionProxyTamperDetection::
+ ReportUMAforContentLengthHeaderValidation() const {
+ // Gets MIME type of the response and reports to UMA histograms separately.
+ // Divides MIME types into 4 groups: JavaScript, CSS, Images, and others.
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength",
+ carrier_id_);
+
+ // Gets MIME type.
+ std::string mime_type;
+ response_headers_->GetMimeType(&mime_type);
+
+ // Reports tampered JavaScript.
+ if (mime_type.compare("text/javascript") == 0 ||
+ mime_type.compare("application/x-javascript") == 0 ||
+ mime_type.compare("application/javascript") == 0) {
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS",
+ carrier_id_);
+ }
+ // Reports tampered CSSs.
+ 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.
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS",
+ carrier_id_);
+ }
+ // Reports tampered images.
+ else if (mime_type.find("image/") == 0) {
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image",
+ carrier_id_);
+ }
+ // Reports tampered other MIME types.
+ else {
+ REPORT_TAMPER_DETECTION_UMA(
+ scheme_is_https_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other",
+ carrier_id_);
+ }
+}
+
+// We construct a canonical representation of the header so that reordered
+// header values will produce the same fingerprint. The fingerprint is
+// constructed as follows:
+// 1) sort the values;
+// 2) concatenate sorted values with a "," delimiter.
+std::string DataReductionProxyTamperDetection::ValuesToSortedString(
+ std::vector<std::string>* values) {
+ std::string concatenated_values;
+ DCHECK(values);
+ if (!values) return "";
+
+ std::sort(values->begin(), values->end());
+ for (size_t i = 0; i < values->size(); ++i) {
+ // Concatenates with delimiter ",".
+ concatenated_values += (*values)[i] + ",";
+ }
+ return concatenated_values;
+}
+
+void DataReductionProxyTamperDetection::GetMD5(
+ const std::string& input, std::string* output) {
+ base::MD5Digest digest;
+ base::MD5Sum(input.c_str(), input.size(), &digest);
+ *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.
+}
+
+std::vector<std::string> DataReductionProxyTamperDetection::GetHeaderValues(
+ 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.
+ std::vector<std::string> values;
+ std::string value;
+ void* iter = NULL;
+ while (headers->EnumerateHeader(&iter, header_name, &value)) {
+ values.push_back(value);
+ }
+ return values;
+}
+
+} // namespace data_reduction_proxy

Powered by Google App Engine
This is Rietveld 408576698