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..24dc6bf52fc5f488753c425a506a120f42e9f3b0 |
--- /dev/null |
+++ b/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc |
@@ -0,0 +1,283 @@ |
+// 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 <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 "net/android/network_library.h" |
+#include "net/http/http_request_headers.h" |
+#include "net/http/http_util.h" |
+ |
+std::vector<std::string> GetHeaderValues( |
+ const net::HttpResponseHeaders* headers, const std::string header_field) { |
+ std::vector<std::string> values; |
+ std::string value; |
+ void* iter = NULL; |
+ while (headers->EnumerateHeader(&iter, header_field, &value)) { |
+ values.push_back(value); |
+ } |
+ return values; |
+} |
+ |
+std::string ValuesToSortedString(std::vector<std::string> &values) { |
+ std::string aggregated_header = ""; |
+ |
+ std::sort(values.begin(), values.end()); |
+ for (size_t i = 0; i < values.size(); ++i) |
+ aggregated_header += values[i] + ","; |
+ return aggregated_header; |
+} |
+ |
+std::string GetMD5(const std::string input) { |
+ 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, 16); |
+} |
+ |
+namespace data_reduction_proxy { |
+ |
+bool CheckHeaderChromeProxy(const std::string f1, |
bolian
2014/06/25 18:55:56
I think it is better to return true in case of pos
xingx
2014/06/25 20:55:43
Done.
|
+ const net::HttpResponseHeaders* headers) { |
+ // I call fingerprint from FW, received_fingerprint; the fingerprint |
+ // generated from contents is called actual_fingerprint |
+ std::string received_f1; |
+ if (!base::Base64Decode(f1, &received_f1)) { |
+ LOG(WARNING) << "Xing f1 base64 decode fails"; // remove later |
+ return true; |
+ } |
+ |
+ // need to get all the values of Chrome-Proxy, remove value fp=xxx, |
+ // and calculate hash value |
+ std::vector<std::string> values = GetHeaderValues(headers, "Chrome-Proxy"); |
+ |
+ for (size_t i = 0; i < values.size(); ++i) |
+ if (values[i].find("fp=") == 0) { |
+ values.erase(values.begin() + i); |
+ break; |
+ } |
+ |
+ // from vector of values of "Chrome-Proxy", forma a string and |
+ // calculate the MD5 value on the string |
+ std::string actual_f1 = GetMD5(ValuesToSortedString(values)); |
+ |
+ return received_f1.compare(actual_f1) == 0; |
+} |
+ |
+bool CheckHeaderVia(const std::string f2, |
+ const net::HttpResponseHeaders* headers) { |
+ |
+ // right now we get f2 value from FW, we may remove this later |
+ // since for FW's f2 value, it should always be 0 |
+ |
+ std::vector<std::string> vias = GetHeaderValues(headers, "via"); |
+ |
+ // exist_chrome represents whether there is Chrome proxy |
+ // in Via header; |
+ // exist_hidden represents whether there is middlebox between |
+ // FW and phone. |
+ //bool exist_chrome = false; |
+ bool exist_hidden = false; |
+ for (int i = vias.size() - 1; i >= 0; --i) |
+ if (vias[i].find("Chrome") != std::string::npos) { |
+ // exist_chrome = true; |
+ exist_hidden = (i < (int)(vias.size() - 1)); |
+ break; |
+ } |
+ |
+ return !exist_hidden; |
+} |
+ |
+bool CheckHeaderOtherHeaders(const std::string f3, |
+ const net::HttpResponseHeaders* headers) { |
+ std::string received_f3; |
+ |
+ // f3 format: |
+ // f3 = [base64fingerprint]:header_name1:header_namer2:... |
+ net::HttpUtil::ValuesIterator it(f3.begin(), f3.end(), ':'); |
+ if (!(it.GetNext() && base::Base64Decode( |
+ std::string(it.value_begin(), it.value_end()), |
+ &received_f3))) { |
+ LOG(WARNING) << "Xing f3 base64 decode fails"; |
+ return true; |
+ } |
+ |
+ // get header value for each header specified in f3 |
+ std::string header_values = ""; |
+ while (it.GetNext()) { |
+ std::vector<std::string> values = GetHeaderValues(headers, |
+ std::string(it.value_begin(), |
+ it.value_end())); |
+ header_values += ValuesToSortedString(values) + ";"; |
+ } |
+ |
+ // calculate actual_f3 |
+ std::string actual_f3 = GetMD5(header_values); |
+ |
+ return received_f3.compare(actual_f3) == 0; |
+} |
+ |
+bool CheckHeaderContentLength(const std::string f4, |
+ const net::HttpResponseHeaders* headers, |
+ std::string* mime_type) { |
+ bool equal = true; |
+ // if content_length from FW is not empty, check; |
+ // otherwise, pass. |
+ if (f4.size()) { |
+ int received_content_length, actual_content_length; |
+ if (!base::StringToInt(f4, &received_content_length)) |
+ return true; |
+ |
+ std::string actual_content_length_; |
+ if (headers->GetNormalizedHeader("Content-Length", |
+ &actual_content_length_)) { |
+ if (!base::StringToInt(actual_content_length_, &actual_content_length)) |
+ return true; |
+ // equal marks whether received length == length sent by FW |
+ equal = (received_content_length == actual_content_length); |
+ } |
+ } |
+ |
+ if (!equal) |
+ headers->GetMimeType(mime_type); |
+ LOG(WARNING) << "xing type "<<*mime_type; |
+ return equal; |
+} |
+ |
+void CheckResponseFingerprint(const net::HttpResponseHeaders* headers, |
+ const bool is_secure_scheme) |
+{ |
+ // schemeIsSecure, we may need to change name, it means it's default |
+ // FW on HTTPS or fallback FW on HTTP |
+ |
+ // put values of Chrome-Proxy into a vector, check if it has a "fp=" value |
+ std::vector<std::string> values = GetHeaderValues(headers, "Chrome-Proxy"); |
+ |
+ // enumerate and check if we see "fp=" value |
+ int fingerprint_index = -1; |
+ for (size_t i=0; i<values.size(); ++i) { |
+ if (values[i].find("fp=") == 0) { |
+ fingerprint_index = i; |
+ break; |
+ } |
+ } |
+ |
+ if (fingerprint_index == -1) |
+ return; |
+ |
+ // delimiter "|", separate fp= string: fp=f1|f2|f3|f4 |
+ net::HttpUtil::ValuesIterator it(values[fingerprint_index].begin() + 3, |
+ values[fingerprint_index].end(), '|'); |
+ |
+ // we found "fp=" value, need to check fingerprint, first get carrier ID |
+ unsigned mcc_mnc = 0; |
+ base::StringToUint(net::android::GetTelephonyNetworkOperator(), &mcc_mnc); |
+ |
+ // log total number for tamper detect |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampereDetected" : |
+ "DataReductionProxy.HTTPHeaderTampereDetected", |
+ mcc_mnc); |
+ |
+ |
+ // check fingerprint one by one |
+ if (!it.GetNext()) return; |
+ if (!CheckHeaderChromeProxy(std::string(it.value_begin(), it.value_end()), |
+ headers)) { |
+ // need to check if it's HTTP or HTTPS |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy" : |
+ "DataReductionProxy.HTTPHeaderTampered_ChromeProxy", |
+ mcc_mnc); |
+ |
+ LOG(WARNING)<<"Xing f1 not equal"; |
+ } |
+ |
+ if (!it.GetNext()) return; |
+ if (!CheckHeaderVia(std::string(it.value_begin(), it.value_end()), |
+ headers)) { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampered_Via" : |
+ "DataReductionProxy.HTTPHeaderTampered_Via", |
+ mcc_mnc); |
+ |
+ LOG(WARNING)<<"Xing f2 not equal"; |
+ } |
+ |
+ if (!it.GetNext()) return; |
+ if (!CheckHeaderOtherHeaders(std::string(it.value_begin(), it.value_end()), |
+ headers)) { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders" : |
+ "DataReductionProxy.HTTPHeaderTampered_OtherHeaders", |
+ mcc_mnc); |
+ LOG(WARNING)<<"Xing f3 not equal"; |
+ } |
+ |
+ std::string mime_type; |
+ if (!it.GetNext()) return; |
+ if (!CheckHeaderContentLength(std::string(it.value_begin(), it.value_end()), |
+ headers, &mime_type)) { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength" : |
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength", |
+ mcc_mnc); |
+ |
+ if (mime_type.compare("text/javascript") == 0 || |
+ mime_type.compare("application/x-javascript") == 0 || |
+ mime_type.compare("application/javascript") == 0) { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS" : |
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS", |
+ mcc_mnc); |
+ |
+ LOG(WARNING) << "Xing mimetype JS"; |
+ } |
+ else if (mime_type.compare("text/css") == 0) { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS" : |
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS", |
+ mcc_mnc); |
+ |
+ LOG(WARNING) << "Xing mimetype CSS"; |
+ } |
+ else if (mime_type.find("image") == 0) { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image" : |
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image", |
+ mcc_mnc); |
+ |
+ LOG(WARNING) << "Xing mimetype Image"; |
+ } |
+ else { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other" : |
+ "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other", |
+ mcc_mnc); |
+ |
+ LOG(WARNING) << "Xing mimetype Other"; |
+ } |
+ LOG(WARNING)<<"Xing f4 not equal"; |
+ } |
+} |
+ |
+} // namespace data_reduction_proxy |