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..26f25ab8608f4af8f126e7bdad3734a18238f3f7 |
--- /dev/null |
+++ b/components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc |
@@ -0,0 +1,281 @@ |
+// 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_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; |
+} |
+ |
+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; |
+} |
+ |
+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); |
bolian
2014/06/27 00:49:02
Is this constant defined somewhere?
|
+} |
+ |
+namespace data_reduction_proxy { |
+ |
+bool CheckHeaderChromeProxy(const std::string fingerprint, |
+ const net::HttpResponseHeaders* headers) { |
+ // I call fingerprint from FW, received_fingerprint; the fingerprint |
bolian
2014/06/27 00:49:02
Don't mention FW. I think readers want to see more
|
+ // generated from contents is called actual_fingerprint |
+ std::string received_fingerprint; |
+ if (!base::Base64Decode(fingerprint, &received_fingerprint)) { |
+ LOG(WARNING) << "Xing f1 base64 decode fails"; // remove later |
bolian
2014/06/27 00:49:02
It is time to remove now. :)
|
+ return false; |
+ } |
+ |
+ // need to get all the values of Chrome-Proxy, remove value fp=xxx, |
bolian
2014/06/27 00:49:02
Capitalize the first letter and add period at the
|
+ // 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_fingerprint = GetMD5(ValuesToSortedString(values)); |
+ |
+ return received_fingerprint.compare(actual_fingerprint) != 0; |
bolian
2014/06/27 00:49:02
return received_fingerprint != actual_fingerprint;
xingx
2014/06/27 16:34:37
Done.
|
+} |
+ |
+bool CheckHeaderVia(const std::string fingerprint, |
+ 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) { |
bolian
2014/06/27 02:16:58
Use the real header value. Expose that from compon
|
+ // exist_chrome = true; |
bolian
2014/06/27 00:49:02
Use HasDataReductionProxyViaHeader in components/d
|
+ exist_hidden = (i < (int)(vias.size() - 1)); |
+ break; |
+ } |
+ |
+ return exist_hidden; |
+} |
+ |
+bool CheckHeaderOtherHeaders(const std::string fingerprint, |
+ const net::HttpResponseHeaders* headers) { |
+ std::string received_fingerprint; |
+ |
+ // f3 format: |
+ // f3 = [base64fingerprint]:header_name1:header_namer2:... |
+ net::HttpUtil::ValuesIterator it(fingerprint.begin(), |
+ fingerprint.end(), ':'); |
+ if (!(it.GetNext() && base::Base64Decode( |
+ std::string(it.value_begin(), it.value_end()), |
bolian
2014/06/27 00:49:02
it.value()?
xingx
2014/06/27 16:34:37
Done.
|
+ &received_fingerprint))) { |
+ LOG(WARNING) << "Xing f3 base64 decode fails"; |
+ return false; |
+ } |
+ |
+ // get header value for each header specified in f3 |
+ std::string header_values = ""; |
bolian
2014/06/27 00:49:02
Remove the empty string literal. That is the defau
xingx
2014/06/27 16:34:37
Done.
|
+ 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_fingerprint = GetMD5(header_values); |
+ |
+ return received_fingerprint.compare(actual_fingerprint) != 0; |
+} |
+ |
+bool CheckHeaderContentLength(const std::string fingerprint, |
+ const net::HttpResponseHeaders* headers) { |
+ bool equal = true; |
+ // if content_length from FW is not empty, check; |
+ // otherwise, pass. |
+ if (fingerprint.size()) { |
+ int received_content_length, actual_content_length; |
+ if (!base::StringToInt(fingerprint, &received_content_length)) |
+ return false; |
+ |
+ std::string actual_content_length_; |
+ if (headers->GetNormalizedHeader("Content-Length", |
+ &actual_content_length_)) { |
+ if (!base::StringToInt(actual_content_length_, &actual_content_length)) |
bolian
2014/06/27 00:49:02
You can just compare the strings.
|
+ return true; |
+ // equal marks whether received length == length sent by FW |
+ equal = (received_content_length == actual_content_length); |
+ } |
+ } |
+ |
+ 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"); |
bolian
2014/06/27 00:49:02
I think we should have a class that wraps these in
|
+ |
+ // 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) { |
bolian
2014/06/27 00:49:02
define fp= and other keywords as constants.
xingx
2014/06/27 16:34:37
Done.
|
+ fingerprint_index = i; |
+ break; |
+ } |
+ } |
+ |
+ if (fingerprint_index == -1) |
+ return; |
+ |
+ // delimiter "|", separate fp= string: fp=f1|f2|f3|f4 |
bolian
2014/06/27 00:49:02
I think it might be better to encode the whole val
bolian
2014/06/27 00:49:02
What is the assumption here? All four values must
|
+ net::HttpUtil::ValuesIterator it(values[fingerprint_index].begin() + 3, |
bolian
2014/06/27 00:49:02
compute 3 from the keyword "fp=" you defined.
xingx
2014/06/27 16:34:37
Done.
|
+ 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" : |
bolian
2014/06/27 00:49:02
The name sounds like tamper happened. How about ..
xingx
2014/06/27 16:34:37
Done.
|
+ "DataReductionProxy.HTTPHeaderTampereDetected", |
+ mcc_mnc); |
+ |
+ |
+ // check fingerprint one by one |
+ if (!it.GetNext()) return; |
+ if (CheckHeaderChromeProxy(std::string(it.value_begin(), it.value_end()), |
+ headers)) { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ is_secure_scheme ? |
+ "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy" : |
+ "DataReductionProxy.HTTPHeaderTampered_ChromeProxy", |
+ mcc_mnc); |
+ |
+ LOG(WARNING)<<"Xing f1 not equal"; |
bolian
2014/06/27 00:49:02
Time to remove all these.
|
+ } |
+ |
+ 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)) { |
+ headers->GetMimeType(&mime_type); |
+ LOG(WARNING) << "xing type "<<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 |