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..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 |