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

Unified Diff: components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc

Issue 338483002: Chrome Participated Tamper Detect (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 5 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_detect.cc
diff --git a/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc b/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d03b1016cb65eb748b003efd3fbe0463ac9ee161
--- /dev/null
+++ b/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc
@@ -0,0 +1,429 @@
+// 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.
+
+// This file implements the tamper detection logic, where we want to detect
+// whether there are middleboxes and whether they are tampering the response
+// which maybe break correct communication and data transfer between Chrome
+// and data reduction proxy.
+//
+// A high-level description of our tamper detection process works in two steps:
+// 1. Data reduction proxy selects the requests we want to detect tamper;
+// for the selected ones, data reduction proxy generates a series of
+// fingerprints of the response, and append them to the Chrome-Proxy header;
+// 2. At Chrome client side, once it sees such fingerprints, it uses the
+// same method of data reduction proxy to generate the fingerprints on
+// the response it receives, compare it to the result on the response
+// data reduction proxy sends, i.e., the attached fingerprints in
+// Chrome-Proxy header, to see if they are identical and report to UMA if
+// there is any tamper detected.
+//
+// Right now we have 4 fingerprints (listed below). Chrome first check the
+// fingerprint of Chrome-Proxy header. If Chrome-Proxy header has been
+// tampered, then other fingerprints would not be checked; if not, Chrome
+// parses the rest of the fingerprints and check whether there is tampering
+// on each of them.
+//
+// 1. Chrome-Proxy header
+// whether values of Chrome-Proxy have been tampered;
+// 2. Via header
+// whether there are middleboxes between Chrome and data reduction proxy;
+// 3. Some other headers
+// whether the values of a list of headers have been tampered;
+// 4. Content-Length header
+// whether the value of Content-Length is different to what data reduction
+// proxy sends, which indicates that the response body has been tampered.
+//
+// Chrome reports tamper or not information for each fingerprint to UMA. In
+// general, Chrome reports the number of tampers for each fingerprint on
+// different carriers, as well as total number of tamper detection handled.
+// The only special case is the 4th fingerprint, Content-Length, which we have
+// another dimension, MIME types, Chrome reports the tamper on different MIME
+// type independently.
+
+
+#include <string.h>
+#include <algorithm>
+#include <vector>
+
+#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/browser/data_reduction_proxy_tamper_detect.h"
+
+#include "net/android/network_library.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_util.h"
+
+// Macro for UMA report. First report to either |https_histogram| or
+// |http_histogram| depends on |scheme_is_https|, with carrier ID as bucket
+// |mcc_mnc|. Then report to http(s)_histogram_Total, which counts the total
+// number.
+#define UMA_REPORT(scheme_is_https, http_histogram, https_histogram, mcc_mnc) \
+ do { \
+ if (scheme_is_https) { \
+ UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, mcc_mnc); \
+ UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
+ } else { \
+ UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, mcc_mnc); \
+ UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
+ }\
+ } while (0)
+
+namespace data_reduction_proxy {
+
+namespace {
+// Two fingerprints will be added to Chrome-Proxy header. One starts with
+// |kTamperDetectFingerprintChromeProxy|, which is the fingerprint for the
+// Chrome-Proxy header. The other one starts with |kTamperDetectFingerprint|,
+// which includes all other fingerprints.
+const char kTamperDetectFingerprint[] = "fp=";
+const char kTamperDetectFingerprintChromeProxy[] = "cp=";
+
+// Fingerprint |kTamperDetectFingerprint| contains multiple
+// fingerprints, each starts with a tag followed by "=" and its fingerprint
+// value. Three fingerprints and their respective tags are defined below.
+const char kTamperDetectFingerprintVia[] = "via";
+const char kTamperDetectFingerprintOther[] = "oh";
+const char kTamperDetectFingerprintContengLength[] = "cl";
+
+} // namespace
+
+void DataReductionProxyTamperDetect::CheckResponseFingerprint(
+ const net::HttpResponseHeaders* headers,
+ const bool is_secure_scheme)
+{
+ std::vector<std::string> values = DataReductionProxyTamperDetect::
+ GetHeaderValues(headers, "Chrome-Proxy");
+
+ // |chrome_proxy_fingerprint| holds the value of fingerprint of
+ // Chrome-Proxy header.
+ // |other_fingerprints| holds the value of other fingerprints.
+ std::string chrome_proxy_fingerprint, other_fingerprints;
+
+ // Check if there are fingerprints (and thus need to detect tamper).
+ if (!ContainsTamperDetectFingerprints(&values,
+ &chrome_proxy_fingerprint,
+ &other_fingerprints))
+ return;
+
+ // Found tamper detect request field.
+ // Get carrier ID.
+ unsigned mcc_mnc = 0;
+ base::StringToUint(net::android::GetTelephonyNetworkOperator(), &mcc_mnc);
+
+ DataReductionProxyTamperDetect tamper_detect(headers, is_secure_scheme,
+ mcc_mnc, &values);
+
+ // Check if Chrome-Proxy header has been tampered.
+ if (tamper_detect.IsChromeProxyHeaderTampered(chrome_proxy_fingerprint)) {
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy",
+ "DataReductionProxy.HTTPHeaderTampered_ChromeProxy",
+ mcc_mnc);
+ return;
+ } else
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTamperDetection",
+ "DataReductionProxy.HTTPHeaderTamperDetection",
+ mcc_mnc);
+
+ // Separate fingerprints from |other_fingerprints|.
+ net::HttpUtil::ValuesIterator it(other_fingerprints.begin(),
+ other_fingerprints.end(), '|');
+
+ // For each fingerprint, get its name |key| and the fingerprint value |value|
+ // from data reduction proxy. CheckReportFingerprint will handle the tamper
+ // detect and corresponding UMA report.
+ size_t delimiter_pos = std::string::npos;
+ while (it.GetNext()) {
+ delimiter_pos = it.value().find("=");
+ if (delimiter_pos == std::string::npos)
+ continue;
+ std::string key = it.value().substr(0, delimiter_pos);
+ std::string value = it.value().substr(delimiter_pos + 1);
+
+ FingerprintCode fingerprint_code = tamper_detect.GetFingerprintCode(key);
+ switch (fingerprint_code) {
+ case VIA:
+ if (tamper_detect.IsViaHeaderTampered(value))
+ tamper_detect.ReportViaHeaderTamperedUMA();
+ break;
+ case OTHERHEADERS:
+ if (tamper_detect.AreOtherHeadersTampered(value))
+ tamper_detect.ReportOtherHeadersTamperedUMA();
+ break;
+ case CONTENTLENGTH:
+ if (tamper_detect.IsContentLengthHeaderTampered(value))
+ tamper_detect.ReportContentLengthHeaderTamperedUMA();
+ break;
+ case CHROMEPROXY:
bolian 2014/07/09 22:51:06 Replace the last two cases with case default:
xingx 2014/07/10 03:07:37 Done.
+ case NONEXIST:
+ break;
+ }
+ }
+ return;
+}
+
+// Initialize fingerprint code map.
+DataReductionProxyTamperDetect::DataReductionProxyTamperDetect(
+ const net::HttpResponseHeaders* headers, const bool secure,
+ const unsigned mcc_mnc_, std::vector<std::string>* values)
+ : response_headers(headers),
+ is_secure_scheme(secure),
+ mcc_mnc(mcc_mnc_),
+ clean_chrome_proxy_header_values(values) {
+ fingperprint_tag_code_map = std::map<std::string, FingerprintCode>();
+ fingperprint_tag_code_map[kTamperDetectFingerprintVia] = VIA;
+ fingperprint_tag_code_map[kTamperDetectFingerprintOther] = OTHERHEADERS;
+ fingperprint_tag_code_map[kTamperDetectFingerprintContengLength] =
+ CONTENTLENGTH;
+};
+
+DataReductionProxyTamperDetect::~DataReductionProxyTamperDetect() {};
+
+// Check whether Chrome-Proxy header has been tampered.
+// |fingerprint| is the fingerprint Chrome received from data reduction proxy,
+// which is Base64 encoded. Decode it first. Calculate the hash value of
+// Chrome-Proxy header. Note that |clean_chrome_proxy_header_values| holds
+// the values of Chrome-Proxy header with its own fingerprint removed,
+// so it's the correct values to be used to calculate fingerprint.
+// Compare calculated fingerprint to the fingerprint from data reduction proxy
+// (the removed value) and see there is tamper detected.
+bool DataReductionProxyTamperDetect::IsChromeProxyHeaderTampered(
+ const std::string& fingerprint) const {
+ std::string received_fingerprint;
+ if (!base::Base64Decode(fingerprint, &received_fingerprint))
bolian 2014/07/09 22:51:05 This should be treated as tampered, right?
xingx 2014/07/10 03:07:40 That's right, my mistake.
+ return false;
+ // Calculate the MD5 hash value of Chrome-Proxy.
+ std::string actual_fingerprint = GetMD5(
+ ValuesToSortedString(*clean_chrome_proxy_header_values));
+
+ return received_fingerprint != actual_fingerprint;
+}
+
+// Check whether there are proxies/middleboxes between Chrome
+// and data reduction proxy. Concretely, it checks whether there are other
+// proxies/middleboxes' name after data reduction proxy's name in Via header.
+bool DataReductionProxyTamperDetect::IsViaHeaderTampered(
+ const std::string& fingerprint) const {
+
+ std::vector<std::string> vias = GetHeaderValues(response_headers, "via");
+
+ // If there is no tag, then data reduction proxy's tag have been removed.
+ if (vias.size() == 0) return true;
+ // Check whether the last proxy/middlebox is data reduction proxy or not.
+
+ bool seen_data_reduction_proxy = false;
+ // TODO(xingx): change 2 to array size of such constant array.
bolian 2014/07/09 22:51:06 why not do it now?
xingx 2014/07/10 03:07:38 Need to figure it out...
+ for (int i = 0; i < 2; ++i) {
+ if (vias[vias.size() - 1].find(kDataReductionProxyViaValues[i]) !=
+ std::string::npos) {
+ seen_data_reduction_proxy = true;
+ break;
+ }
+ }
+
+ return !seen_data_reduction_proxy;
+}
+
+// Report Via header tamper detected.
+void DataReductionProxyTamperDetect::ReportViaHeaderTamperedUMA() const {
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_Via",
+ "DataReductionProxy.HTTPHeaderTampered_Via",
+ mcc_mnc);
+}
+
+// Check whether values of a predefined list of headers have been tampered.
+// The format for |fingerprint| is:
+// [base64fingerprint]:header_name1:header_namer2:...
+// Firstly extract the header names in the |fingerprint|.
+// For each header,
+// 1) get all the values of such header;
+// 2) sort the values alphabetically;
+// 3) concatenate sorted values to a string and calculate MD5 hash on it.
+// Finally, compare whether it equals to the received fingerprint.
+bool DataReductionProxyTamperDetect::AreOtherHeadersTampered(
+ const std::string& fingerprint) const {
+ std::string received_fingerprint;
+
+ net::HttpUtil::ValuesIterator it(fingerprint.begin(),
+ fingerprint.end(), ':');
+
+ // Make sure there is [base64fingerprint] and it can be decoded.
+ if (!(it.GetNext() &&
bolian 2014/07/09 22:51:06 Will the server always send "oh=" even if the head
xingx 2014/07/10 03:07:37 Yes. Done.
+ base::Base64Decode(std::string(it.value()), &received_fingerprint)))
+ return false;
+
+ std::string header_values;
+ // Enumerate the list of headers.
+ while (it.GetNext()) {
+ // Get values of one header.
+ std::vector<std::string> values =
+ GetHeaderValues(response_headers, std::string(it.value()));
+ // Sort the values and concatenate them.
+ header_values += ValuesToSortedString(values) + ";";
+ }
+
+ // Calculate MD5 hash value on the concatenated string.
+ std::string actual_fingerprint = GetMD5(header_values);
+
+ return received_fingerprint != actual_fingerprint;
+}
+
+// Report other headers tamper detected.
+void DataReductionProxyTamperDetect::ReportOtherHeadersTamperedUMA() const {
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders",
+ "DataReductionProxy.HTTPHeaderTampered_OtherHeaders",
+ mcc_mnc);
+}
+
+
+// For Content-Length tamper detection...
+// Check whether the Content-Length value is different from what
+// data reduction proxy sees. This is an indicator that the response body
+// have been modified.
+// It's modified only if we can decode Content-Length numbers at both end
+// and such two numbers are not equal.
+bool DataReductionProxyTamperDetect::IsContentLengthHeaderTampered(
+ const std::string& fingerprint) const {
+ int received_content_length, actual_content_length;
+ // If Content-Length value from data reduction proxy is not available or
+ // it cannot be converted to integer, pass.
+ if (base::StringToInt(fingerprint, &received_content_length)) {
+ std::string actual_content_length_;
+ // If there is Content-Length at Chrome client side is not available, pass.
+ if (response_headers->GetNormalizedHeader("Content-Length",
+ &actual_content_length_)) {
+ // If the Content-Length value cannot be converted to integer,
+ // i.e., not valid, pass.
+ if (!base::StringToInt(actual_content_length_, &actual_content_length))
+ return false;
+ return received_content_length != actual_content_length;
+ }
+ }
+ return false;
+}
+
+// Report Content-Length tamper detected.
+// Get MIME type of the response and report to different UMA histogram.
+// Right now MIME types contain JavaScript, CSS, Images, and others.
+void DataReductionProxyTamperDetect::ReportContentLengthHeaderTamperedUMA()
+ const {
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength",
+ mcc_mnc);
+
+ // Get MIME type.
+ std::string mime_type;
+ response_headers->GetMimeType(&mime_type);
+
+
+ // Report tampered JavaScript.
+ if (mime_type.compare("text/javascript") == 0 ||
+ mime_type.compare("application/x-javascript") == 0 ||
+ mime_type.compare("application/javascript") == 0)
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS",
+ mcc_mnc);
+ // Report tampered CSSs.
+ else if (mime_type.compare("text/css") == 0)
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS",
+ mcc_mnc);
+ // Report tampered images.
+ else if (mime_type.find("image") == 0)
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image",
+ mcc_mnc);
+ // Report tampered other MIME types.
+ else
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other",
+ mcc_mnc);
+}
+
+DataReductionProxyTamperDetect::FingerprintCode
+ DataReductionProxyTamperDetect::GetFingerprintCode(
+ const std::string& tag) {
+ std::map<std::string, DataReductionProxyTamperDetect::FingerprintCode>
+ ::iterator it = fingperprint_tag_code_map.find(tag);
+
+ return it == fingperprint_tag_code_map.end() ? NONEXIST : it->second;
+}
+
+// Enumerate the values of Chrome-Proxy header and check if there is the
+// fingerprint of Chrome-Proxy header (kTamperDetectFingerprintChromeProxy)
+// and other fingerprints (kTamperDetectFingerprint). If there are, save them
+// accordingly.
+bool DataReductionProxyTamperDetect::ContainsTamperDetectFingerprints(
bolian 2014/07/09 22:51:06 Rename this. The name sounds like it won't change
xingx 2014/07/10 03:07:40 Done.
+ std::vector<std::string>* values,
+ std::string* chrome_proxy_fingerprint,
+ std::string* other_fingerprints) {
+ bool contains_tamper_detect_fingerprints = false;
+ for (size_t i = 0; i < values->size(); ++i) {
+ if ((*values)[i].find(data_reduction_proxy::
bolian 2014/07/09 22:51:06 s/data_reduction_proxy::// This is in the same nam
xingx 2014/07/10 03:07:37 Done.
+ kTamperDetectFingerprintChromeProxy) == 0) {
+ contains_tamper_detect_fingerprints = true;
+ // Save Chrome-Proxy fingerprint.
+ *chrome_proxy_fingerprint = (*values)[i].substr(strlen(
+ data_reduction_proxy::kTamperDetectFingerprintChromeProxy));
+ // Erase Chrome-Proxy's fingerprint from Chrome-Proxy header for
+ // later fingerprint calculation.
+ values->erase(values->begin() + i);
+ // Adjust index |i| after erasing.
+ --i;
bolian 2014/07/09 22:51:06 Don't change the vector while iterating. Do it out
xingx 2014/07/10 03:07:38 Done.
+ }
+ else if ((*values)[i].find(data_reduction_proxy::kTamperDetectFingerprint)
+ == 0) {
+ // Save other fingerprints.
+ *other_fingerprints = (*values)[i].substr(strlen(data_reduction_proxy::
+ kTamperDetectFingerprint));
+ }
+ }
+ return contains_tamper_detect_fingerprints;
+}
+
+// Utility function. Sort the strings in |values| alphabetically, concatenate
+// them into a string.
+std::string DataReductionProxyTamperDetect::ValuesToSortedString(
+ std::vector<std::string> &values) {
+ std::string aggregated_values;
+
+ std::sort(values.begin(), values.end());
+ for (size_t i = 0; i < values.size(); ++i)
+ aggregated_values += values[i] + ",";
+ return aggregated_values;
+}
+
+// Utility function. For a given string, calculate and return the MD5 hash
+// value of the string.
+std::string DataReductionProxyTamperDetect::GetMD5(const std::string &input) {
+ base::MD5Digest digest;
+ base::MD5Sum(input.c_str(), input.size(), &digest);
+ return std::string((char*)digest.a, ARRAYSIZE_UNSAFE(digest.a));
+}
+
+// Utility function. For a given |header_name|, get all its values and return
+// the vector contains all of the values.
+std::vector<std::string> DataReductionProxyTamperDetect::GetHeaderValues(
+ const net::HttpResponseHeaders* headers, const std::string& header_name) {
+ 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