Chromium Code Reviews| 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 |