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

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, 6 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..ce028398a5a00c659f0d5069350649e305487461
--- /dev/null
+++ b/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc
@@ -0,0 +1,395 @@
+// 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 between Chrome and data reduction
bengr 2014/07/02 17:30:59 and the data...
xingx 2014/07/06 03:18:18 Not sure if I get it right, change to "break corre
+// 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 it 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 if there is
+// any tamper detected to UMA.
+//
+// Right now we have 4 fingerprints. Chrome parses the fingerprints, check
+// whether there is tampering on each of them, and report the result to UMA:
+// 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.
+//
+// Then Chrome reports tamper or not information 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 one, 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/sparse_histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.h"
+#include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
+
+#include "net/android/network_library.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_util.h"
+
+// Utility function...
+// Sort the strings in |values| alphabetically, concatenate them into a string.
+std::string 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;
+}
+
+namespace data_reduction_proxy {
+
+// Utility function...
+// For a given string, calculate and return the MD5 hash value of the string.
+std::string GetMD5(const std::string &input) {
bengr 2014/07/02 17:30:59 Look at https://code.google.com/p/chromium/codesea
xingx 2014/07/06 03:18:18 We can discuss about this, right now I'm using bas
+ base::MD5Context context;
+ base::MD5Init(&context);
+ base::MD5Update(&context, input);
+ base::MD5Digest new_digest;
+ base::MD5Final(&new_digest, &context);
+ return std::string((char*)new_digest.a, ARRAYSIZE_UNSAFE(new_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> 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;
+}
+
+// Utility function, exposed for unittest.
+// For Chrome-Proxy header values |values|, check whether it contains two
+// fingerprints:
+// |kTamperDetectFingerprintChromeProxy| and |kTamperDetectFingerprint|.
+// If not, means that there is no tamper detect request, return false;
+// otherwise save these two fingerprints to:
+// |chrome_proxy_fingerprint| and |other_fingerprints|
+// for later use and return true.
+bool ContainsTamperDetectFingerprints(std::vector<std::string>& values,
bolian 2014/07/02 23:47:37 Don't repeat func doc from the .h file. For expos
xingx 2014/07/06 03:18:18 Done.
+ std::string& chrome_proxy_fingerprint,
+ std::string& other_fingerprints) {
+ // Enumerate the values of Chrome-Proxy header and check if there is the
+ // fingerprint of Chrome-Proxy header (kTamperDetectFingerprintChromeProxy)
+ // and other fingerprints (kTamperDetectFingerprint)
+ bool contains_tamper_detect_fingerprints = false;
+ for (size_t i = 0; i < values.size(); ++i) {
+ if (values[i].find(kTamperDetectFingerprintChromeProxy) == 0) {
+ contains_tamper_detect_fingerprints = true;
+ // Save Chrome-Proxy fingerprint.
+ chrome_proxy_fingerprint = values[i].
+ substr(strlen(kTamperDetectFingerprintChromeProxy));
+ // Erase Chrome-Proxy's fingerprint from Chrome-Proxy header for
+ // later fingerprint calculation.
+ values.erase(values.begin() + (i--));
bengr 2014/07/02 17:30:59 Separate out the decrement and explain why it is n
xingx 2014/07/06 03:18:18 Done.
+ }
+ else if (values[i].find(kTamperDetectFingerprint) == 0)
bengr 2014/07/02 17:30:59 Add curly braces.
xingx 2014/07/06 03:18:18 Done.
+ // Save other fingerprints.
+ other_fingerprints = values[i].substr(strlen(kTamperDetectFingerprint));
+ }
+ return contains_tamper_detect_fingerprints;
+}
+
+// The main function for detecting tamper.
+// For such response, the function checks whether there is a tamper detect
+// request from data reduction proxy.
+// if so, it checks whether there are tampers for each fingerprint one by one
+// and report the results to UMA.
+void CheckResponseFingerprint(const net::HttpResponseHeaders* headers,
+ const bool is_secure_scheme)
+{
+ // Get all the values of Chrome-Proxy header.
bolian 2014/07/02 23:47:37 rm doc here. This is obvious from reading the code
xingx 2014/07/06 03:18:18 Done.
+ std::vector<std::string> values = 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);
+
+ // Initialize tamper detect object.
bolian 2014/07/02 23:47:37 rm. Useless comment.
xingx 2014/07/06 03:18:18 Done.
+ DataReductionProxyTamperDetect tamper_detect(headers, is_secure_scheme,
+ mcc_mnc, &values);
+
+ // Check if Chrome-Proxy header has been tampered.
+ if (tamper_detect.CheckHeaderChromeProxy(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);
+ tamper_detect.CheckReportFingerprint(key, value);
+ }
+ return;
+}
+
+// Constructor of DataReductionProxyTamperDetect class.
bolian 2014/07/02 23:47:37 delete "// Constructor of DataReductionProxyTamper
xingx 2014/07/06 03:18:18 Done.
+// It initialize the function pointer map.
+// Right now we have 3 fingerprints to check (besides Chrome-Proxy header's
+// fingerprint, which has been handled specially.
+// In the future we can add more fingerprints to check, need to implement
+// a pair of functions:
+// checking and reporting, and then add it to the function 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) {
+ check_report_func_map = std::map<std::string, CheckReportFuncs>();
+
+ check_report_func_map[kTamperDetectFingerprintVia] =
+ {&DataReductionProxyTamperDetect::CheckHeaderVia,
+ &DataReductionProxyTamperDetect::ReportHeaderVia};
+
+ check_report_func_map[kTamperDetectFingerprintOther] =
+ {&DataReductionProxyTamperDetect::CheckHeaderOtherHeaders,
+ &DataReductionProxyTamperDetect::ReportHeaderOtherHeaders};
+
+ check_report_func_map[kTamperDetectFingerprintContengLength] =
+ {&DataReductionProxyTamperDetect::CheckHeaderContentLength,
+ &DataReductionProxyTamperDetect::ReportHeaderContentLength};
+};
+
+DataReductionProxyTamperDetect::~DataReductionProxyTamperDetect() {};
+
+// For fingerprint name tag |key|, call it's corresponding checking function
+// and reporting function.
+void DataReductionProxyTamperDetect::CheckReportFingerprint(
+ const std::string& key, const std::string& fingerprint) {
+ CheckReportFuncs funcs = check_report_func_map[key];
+ if ((this->*funcs.check_tamper_func)(fingerprint))
bengr 2014/07/02 17:30:59 use base::Callback
xingx 2014/07/06 03:18:18 Changed to switch / enum.
+ (this->*funcs.report_tamper_func)();
+}
+
+// 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::CheckHeaderChromeProxy(
+ const std::string& fingerprint) {
+ std::string received_fingerprint;
+ if (!base::Base64Decode(fingerprint, &received_fingerprint))
+ return false;
+
+ // Calculate the MD5 hash value of Chrome-Proxy.
+ std::string actual_fingerprint = GetMD5(
+ ValuesToSortedString(*clean_chrome_proxy_header_values));
+
+ // Compare and check if there is tamper detected.
+ return received_fingerprint != actual_fingerprint;
+}
+
+// For Via header tamper detection...
+// 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::CheckHeaderVia(
+ const std::string& fingerprint) {
+
+ 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.
+ return vias[vias.size() - 1].
+ find(kDataReductionProxyViaValue) == std::string::npos;
+}
+
+// Report Via header tamper detected.
+void DataReductionProxyTamperDetect::ReportHeaderVia() {
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_Via",
+ "DataReductionProxy.HTTPHeaderTampered_Via",
+ mcc_mnc);
+}
+
+
+// For other headers tamper detection...
+// 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) we get all the values of such header;
+// 2) we sort the values alphabetically;
+// 3) we concatenate sorted values to a string and calculate MD5 hash on it.
+// Finally, we compare whether it equals to the fingerprint from
+// data reduction proxy.
+bool DataReductionProxyTamperDetect::CheckHeaderOtherHeaders(
+ const std::string& fingerprint) {
bengr 2014/07/02 17:30:59 indent 4.
xingx 2014/07/06 03:18:18 Done.
+ 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() &&
+ 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,
bengr 2014/07/02 17:30:59 move GetHeaderValues to next line and indent 4.
xingx 2014/07/06 03:18:18 Done.
+ 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::ReportHeaderOtherHeaders() {
+ 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::CheckHeaderContentLength(
+ const std::string& fingerprint) {
+ 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;
+ }
+ }
+ else
+ {
+ LOG(WARNING) << "xing can't convert";
bengr 2014/07/02 17:30:59 remove.
xingx 2014/07/06 03:18:18 Done.
+ }
+ 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::ReportHeaderContentLength() {
+ std::string mime_type;
+ // Get MIME type.
+ response_headers->GetMimeType(&mime_type);
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength",
+ mcc_mnc);
+
+ // 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);
+}
+
+
bengr 2014/07/02 17:30:59 remove blank line.
xingx 2014/07/06 03:18:18 Done.
+} // namespace data_reduction_proxy

Powered by Google App Engine
This is Rietveld 408576698