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

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: Move modification on _headers.h/cc to another CL. 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..cca45e17f21a2c3a543a1158866f4177c5c69d9f
--- /dev/null
+++ b/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc
@@ -0,0 +1,402 @@
+// 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_detect.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/android/network_library.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+// Macro for UMA reporting. First reporting to either |https_histogram| or
+// |http_histogram| depending on |scheme_is_https|, with |carrier_id| as bucket
+// which counts for each carrier, how many responses have been tampered with.
+// Then reporting to |http(s)_histogram|_Total, which counts the total number of
+// detected tampering (sum of tampering detected for all carriers).
+#define UMA_REPORT(scheme_is_https, http_histogram, https_histogram, carrier_id) \
+ 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 {
+// Four fingerprints will be added to Chrome-Proxy header. One starts with
+// |kTamperDetectFingerprintChromeProxy|, which is the fingerprint for the
+// Chrome-Proxy header. The other three have been put together into a string
+// starts with key tag |kTamperDetectFingerprint|.
+const char kTamperDetectFingerprints[] = "fp";
bengr 2014/07/16 21:24:48 To avoid name conflicts, all chrome-proxy actions
xingx 2014/07/18 17:25:01 Done.
+const char kTamperDetectFingerprintChromeProxy[] = "cp";
+
+// Fingerprint |kTamperDetectFingerprint| contains three fingerprints, each
+// starts with a key 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
bengr 2014/07/16 21:24:48 Add a space before //
xingx 2014/07/18 17:25:01 Done.
+
+namespace data_reduction_proxy {
+
+// static
+void DataReductionProxyTamperDetection::CheckResponseFingerprint(
+ const net::HttpResponseHeaders* headers,
bengr 2014/07/16 21:24:47 check that headers is non-NULL.
xingx 2014/07/18 17:25:01 Done.
+ const bool is_secure_scheme)
bengr 2014/07/16 21:24:48 can this function be const?
xingx 2014/07/18 17:25:01 Right now the function is static, so can't be cons
+{
bengr 2014/07/16 21:24:48 Move the curly up a line.
xingx 2014/07/18 17:25:01 Done.
+ // If Chrome-Proxy header fingerprint absents, quits tamper detection ASAP.
bengr 2014/07/16 21:24:48 If the Chrome-Proxy header fingerprint is absent,
xingx 2014/07/18 17:25:01 Done.
+ if (!GetDataReductionProxyActionValue(
+ headers, kTamperDetectFingerprintChromeProxy, NULL))
+ return;
+
+ // Found tamper detection request field. Saves Chrome-Proxy header values to
+ // get fingerprints.
+ std::vector<std::string> values = GetHeaderValues(headers, "Chrome-Proxy");
bengr 2014/07/16 21:24:47 I still don't understand why you need this.
bengr 2014/07/16 21:24:47 values -> chrome_proxy_header_values
xingx 2014/07/18 17:25:01 Acknowledged.
xingx 2014/07/18 17:25:02 Acknowledged.
+
+ // |chrome_proxy_fingerprint| holds the value of fingerprint of Chrome-Proxy
+ // header. |other_fingerprints| holds the value of other fingerprints.
+ // |values| saves all the values of Chrome-Proxy header but with fingerprint
+ // for Chrome-Proxy header removed.
+ std::string chrome_proxy_fingerprint, other_fingerprints;
+ if (!GetTamperDetectionFingerprints(&values,
+ &chrome_proxy_fingerprint,
+ &other_fingerprints))
+ return;
bengr 2014/07/16 21:24:48 I would add curly braces around this.
xingx 2014/07/18 17:25:01 Done.
+
+ // Get carrier ID.
+ unsigned carrier_id = 0;
+#if defined(OS_ANDROID)
bengr 2014/07/16 21:24:47 Is this busted on other platforms?
xingx 2014/07/18 17:25:01 Discussed with Bolian, right now for other platfor
+ base::StringToUint(net::android::GetTelephonyNetworkOperator(), &carrier_id);
+#endif
+
+ DataReductionProxyTamperDetection tamper_detection(headers,
+ is_secure_scheme,
+ carrier_id,
+ &values);
+
+ // Check if Chrome-Proxy header has been tampered with.
+ if (tamper_detection.IsChromeProxyHeaderTampered(chrome_proxy_fingerprint)) {
+ tamper_detection.ReportChromeProxyHeaderTamperedUMA();
+ return;
+ } else {
+ UMA_REPORT(is_secure_scheme,
+ "DataReductionProxy.HTTPSHeaderTamperDetection",
+ "DataReductionProxy.HTTPHeaderTamperDetection",
+ carrier_id);
+ }
+
+ // Separate fingerprints from |other_fingerprints|, with delimiter "|".
+ net::HttpUtil::ValuesIterator it(
+ other_fingerprints.begin(), other_fingerprints.end(), '|');
bengr 2014/07/16 21:24:48 Are fingerprints guaranteed not to contain '|'. Sa
xingx 2014/07/18 17:25:02 Modified the design.
+
+ // For each fingerprint, get its name |key| and the fingerprint |value|
bengr 2014/07/16 21:24:48 |value|.
xingx 2014/07/18 17:25:02 Done.
+ 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);
bengr 2014/07/16 21:24:48 Make each fingerprint a Chrome-Proxy action starti
xingx 2014/07/18 17:25:01 Done.
+
+ FingerprintCode fingerprint_code = tamper_detection.GetFingerprintCode(key);
+ switch (fingerprint_code) {
+ case VIA:
+ if (tamper_detection.IsViaHeaderTampered(value))
+ tamper_detection.ReportViaHeaderTamperedUMA();
+ break;
+ case OTHERHEADERS:
+ if (tamper_detection.AreOtherHeadersTampered(value))
+ tamper_detection.ReportOtherHeadersTamperedUMA();
+ break;
+ case CONTENTLENGTH:
+ if (tamper_detection.IsContentLengthHeaderTampered(value))
+ tamper_detection.ReportContentLengthHeaderTamperedUMA();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ return;
+}
+
+// Constructor initializes fingerprint code map.
+DataReductionProxyTamperDetection::DataReductionProxyTamperDetection(
+ const net::HttpResponseHeaders* headers,
bengr 2014/07/16 21:24:48 DCHECK that headers is non-NULL
xingx 2014/07/18 17:25:01 Done.
+ const bool is_secure,
+ const unsigned carrier_id,
+ std::vector<std::string>* values)
+ : response_headers_(headers),
+ is_secure_scheme_(is_secure),
+ carrier_id_(carrier_id),
+ 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;
+};
+
+DataReductionProxyTamperDetection::~DataReductionProxyTamperDetection() {};
+
+// Checks whether the Chrome-Proxy header has been tampered with. |fingerprint|
+// is the fingerprint received from data reduction proxy, which is Base64
+// encoded. Decodes it first. Calculates 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. Compares calculated fingerprint
+// to the fingerprint from data reduction proxy (the removed value) and see
+// there is tamper detected or not.
+bool DataReductionProxyTamperDetection::IsChromeProxyHeaderTampered(
+ const std::string& fingerprint) const {
+ std::string received_fingerprint;
+ if (!base::Base64Decode(fingerprint, &received_fingerprint))
+ return true;
+ // Calculate the MD5 hash value of Chrome-Proxy.
+ std::string actual_fingerprint = GetMD5(
+ ValuesToSortedString(clean_chrome_proxy_header_values_));
+
+ return received_fingerprint != actual_fingerprint;
+}
+
+// Reports Chrome-Proxy header tamper detected.
+void DataReductionProxyTamperDetection::ReportChromeProxyHeaderTamperedUMA()
+ const {
+ UMA_REPORT(is_secure_scheme_,
bengr 2014/07/16 21:24:47 I'd rename the macro to be less general sounding.
xingx 2014/07/18 17:25:01 Done.
+ "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy",
+ "DataReductionProxy.HTTPHeaderTampered_ChromeProxy",
+ carrier_id_);
+}
+
+// Checks whether there are other proxies/middleboxes' name after the data
+// reduction proxy's name in Via header.
+bool DataReductionProxyTamperDetection::IsViaHeaderTampered(
+ const std::string& fingerprint) const {
+ bool has, is_the_last;
+ has = HasDataReductionProxyViaHeader(response_headers_, &is_the_last);
+
+ // Via header of the data reductoin proxy is missing.
bengr 2014/07/16 21:24:47 spelling
xingx 2014/07/18 17:25:01 Done.
+ if (!has) {
+ UMA_REPORT(is_secure_scheme_,
+ "DataReductionProxy.HTTPSHeaderTampered_Via_Missing",
+ "DataReductionProxy.HTTPHeaderTampered_Via_Missing",
+ carrier_id_);
+ return false;
+ }
+
+ return !is_the_last;
+}
+
+// Reports Via header tamper detected.
+void DataReductionProxyTamperDetection::ReportViaHeaderTamperedUMA() const {
+ UMA_REPORT(is_secure_scheme_,
+ "DataReductionProxy.HTTPSHeaderTampered_Via",
+ "DataReductionProxy.HTTPHeaderTampered_Via",
+ carrier_id_);
+}
+
+// Checks whether values of a predefined list of headers have been tampered.
+// The format for |fingerprint| is:
+// [base64encoded_fingerprint]:header_name1:header_namer2:...
+// Firstly extract the header names in the |fingerprint|. For each header, get
+// all the values of such header, sort the values and concatenate them to a
+// string. Concatenate the string for all the headers (with delimiter ";") and
+// calculate the MD5 hash value on it. Compare such hash value to the
+// fingerprint received from data reduction proxy.
+bool DataReductionProxyTamperDetection::AreOtherHeadersTampered(
+ const std::string& fingerprint) const {
+ std::string received_fingerprint;
+
+ // Fingerprint should never be empty.
+ DCHECK(fingerprint.size());
+
+ net::HttpUtil::ValuesIterator it(fingerprint.begin(),
+ fingerprint.end(), ':');
+
+ // The first value from fingerprint is the actual fingerprint; the following
+ // values are the header names will be included for fingerprint calculation.
+ // Make sure there is [base64fingerprint] and it can be decoded.
+ if (!(it.GetNext() &&
+ base::Base64Decode(it.value(), &received_fingerprint))) {
+ NOTREACHED();
+ return true;
+ }
+
+ std::string header_values;
+ // Enumerate the list of headers.
+ while (it.GetNext()) {
+ // Get values of one header.
+ std::vector<std::string> values =
bengr 2014/07/16 21:24:48 rename as response_header_values
xingx 2014/07/18 17:25:01 Done.
+ GetHeaderValues(response_headers_, it.value());
+ // Sort the values and concatenate them.
+ header_values += ValuesToSortedString(&values) + ";";
bengr 2014/07/16 21:24:47 are the values guaranteed not to contain ';'? Add
xingx 2014/07/18 17:25:02 Done.
+ }
+
+ // Calculate MD5 hash value on the concatenated string.
+ std::string actual_fingerprint = GetMD5(header_values);
+
+ return received_fingerprint != actual_fingerprint;
+}
+
+// Reports other headers tamper detected.
+void DataReductionProxyTamperDetection::ReportOtherHeadersTamperedUMA() const {
+ UMA_REPORT(is_secure_scheme_,
+ "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders",
+ "DataReductionProxy.HTTPHeaderTampered_OtherHeaders",
+ carrier_id_);
+}
+
+// Checks whether the Content-Length value is different from what data reduction
+// proxy sees. Reports it as modified only if Content-Length can be decoded as
+// an interger at both end and such two numbers are not equal.
+bool DataReductionProxyTamperDetection::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 an integer, pass.
+ if (base::StringToInt(fingerprint, &received_content_length)) {
+ std::string actual_content_length_;
+ // If there is no Content-Length header received, pass.
+ if (response_headers_->GetNormalizedHeader("Content-Length",
+ &actual_content_length_)) {
bengr 2014/07/16 21:24:47 indentation.
xingx 2014/07/18 17:25:02 Done.
+ // 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;
+}
+
+// Reports Content-Length tamper detected.
+// Gets MIME type of the response and report to different UMA histogram.
+// Right now MIME types contain JavaScript, CSS, Images, and others.
+void DataReductionProxyTamperDetection::ReportContentLengthHeaderTamperedUMA()
+ const {
+ UMA_REPORT(is_secure_scheme_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength",
+ carrier_id_);
+
+ // Get 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)
+ UMA_REPORT(is_secure_scheme_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS",
+ carrier_id_);
+ // Reports tampered CSSs.
+ else if (mime_type.compare("text/css") == 0)
+ UMA_REPORT(is_secure_scheme_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS",
+ carrier_id_);
+ // Reports tampered images.
+ else if (mime_type.find("image") == 0)
+ UMA_REPORT(is_secure_scheme_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image",
+ carrier_id_);
+ // Reports tampered other MIME types.
+ else
+ UMA_REPORT(is_secure_scheme_,
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other",
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other",
+ carrier_id_);
+}
+
+DataReductionProxyTamperDetection::FingerprintCode
+ DataReductionProxyTamperDetection::GetFingerprintCode(
+ const std::string& tag) {
+ std::map<std::string, DataReductionProxyTamperDetection::FingerprintCode>
+ ::iterator it = fingperprint_tag_code_map_.find(tag);
+
+ return it == fingperprint_tag_code_map_.end() ? NONEXIST : it->second;
+}
+
+// Enumerates the values of Chrome-Proxy header and checks if there are
bengr 2014/07/16 21:24:48 Move method comments to the .h.
xingx 2014/07/18 17:25:01 Function removed.
+// fingerprints for Chrome-Proxy header (kTamperDetectFingerprintChromeProxy)
+// and other fingerprints (kTamperDetectFingerprint). If there are, saves them
+// accordingly, removes fingerprint for Chrome-Proxy header from |values| and
+// return true, otherwise return false.
+bool DataReductionProxyTamperDetection::GetTamperDetectionFingerprints(
+ std::vector<std::string>* values,
bengr 2014/07/16 21:24:47 DCHECK that values is non-NULL.
xingx 2014/07/18 17:25:02 Function removed.
+ std::string* chrome_proxy_fingerprint,
+ std::string* other_fingerprints) {
+ int chrome_proxy_fingerprint_index = -1;
+
+ size_t size = values->size();
+ for (size_t i = 0; i < size; ++i) {
+ if ((*values)[i].find(kTamperDetectFingerprintChromeProxy) == 0 &&
+ (*values)[i][strlen(kTamperDetectFingerprintChromeProxy)] == '=') {
+ chrome_proxy_fingerprint_index = i;
+ // Saves Chrome-Proxy fingerprint.
+ *chrome_proxy_fingerprint = (*values)[i].substr(strlen(
+ kTamperDetectFingerprintChromeProxy));
+ }
+ else if ((*values)[i].find(kTamperDetectFingerprints) == 0 &&
+ (*values)[i][strlen(kTamperDetectFingerprints)] == '=') {
+ // Saves other fingerprints.
+ *other_fingerprints = (*values)[i].substr(
+ strlen(kTamperDetectFingerprints));
+ }
+ }
+
+ if (chrome_proxy_fingerprint_index == -1)
+ return false;
+
+ // Erases Chrome-Proxy's fingerprint from Chrome-Proxy header for
+ // later fingerprint calculation.
+ values->erase(values->begin() + chrome_proxy_fingerprint_index);
+ return true;
+}
+
+// Sorts the strings in |values| and concatenates them into a string.
+std::string DataReductionProxyTamperDetection::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;
+}
+
+// For a given string, calculates and returns the MD5 hash value of the string.
+std::string DataReductionProxyTamperDetection::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));
+}
+
+// For a given |header_name|, gets all its values and returns them as a vector.
+std::vector<std::string> DataReductionProxyTamperDetection::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