Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 // This file implements the tamper detection logic, where we want to detect | |
| 6 // whether there are middleboxes and whether they are tampering the response | |
| 7 // which maybe break correct communication between Chrome and data reduction | |
|
bengr
2014/07/02 17:30:59
and the data...
xingx
2014/07/06 03:18:18
Not sure if I get it right, change to "break corre
| |
| 8 // proxy. | |
| 9 // | |
| 10 // A high-level description of our tamper detection process works in two steps: | |
| 11 // 1. Data reduction proxy selects the requests we want to detect tamper; | |
| 12 // for the selected ones, data reduction proxy generates a series of | |
| 13 // fingerprints of the response, and append it to the Chrome-Proxy header; | |
| 14 // 2. At Chrome client side, once it sees such fingerprints, it uses the | |
| 15 // same method of data reduction proxy to generate the fingerprints on | |
| 16 // the response it receives, compare it to the result on the response | |
| 17 // data reduction proxy sends, i.e., the attached fingerprints in | |
| 18 // Chrome-Proxy header, to see if they are identical and report if there is | |
| 19 // any tamper detected to UMA. | |
| 20 // | |
| 21 // Right now we have 4 fingerprints. Chrome parses the fingerprints, check | |
| 22 // whether there is tampering on each of them, and report the result to UMA: | |
| 23 // 1. Chrome-Proxy header | |
| 24 // whether values of Chrome-Proxy have been tampered; | |
| 25 // 2. Via header | |
| 26 // whether there are middleboxes between Chrome and data reduction proxy; | |
| 27 // 3. Some other headers | |
| 28 // whether the values of a list of headers have been tampered; | |
| 29 // 4. Content-Length header | |
| 30 // whether the value of Content-Length is different to what data reduction | |
| 31 // proxy sends, which indicates that the response body has been tampered. | |
| 32 // | |
| 33 // Then Chrome reports tamper or not information to UMA. | |
| 34 // In general, Chrome reports the number of tampers for each fingerprint | |
| 35 // on different carriers, as well as total number of tamper detection handled. | |
| 36 // The only special case is the 4th one, Content-Length, | |
| 37 // which we have another dimension, MIME types, Chrome reports the tamper on | |
| 38 // different MIME type independently. | |
| 39 | |
| 40 | |
| 41 #include <string.h> | |
| 42 #include <algorithm> | |
| 43 #include <vector> | |
| 44 | |
| 45 #include "base/base64.h" | |
| 46 #include "base/md5.h" | |
| 47 #include "base/metrics/sparse_histogram.h" | |
| 48 #include "base/strings/string_number_conversions.h" | |
| 49 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_de tect.h" | |
| 50 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h" | |
| 51 | |
| 52 #include "net/android/network_library.h" | |
| 53 #include "net/http/http_request_headers.h" | |
| 54 #include "net/http/http_util.h" | |
| 55 | |
| 56 // Utility function... | |
| 57 // Sort the strings in |values| alphabetically, concatenate them into a string. | |
| 58 std::string ValuesToSortedString(std::vector<std::string> &values) { | |
| 59 std::string aggregated_values; | |
| 60 | |
| 61 std::sort(values.begin(), values.end()); | |
| 62 for (size_t i = 0; i < values.size(); ++i) | |
| 63 aggregated_values += values[i] + ","; | |
| 64 return aggregated_values; | |
| 65 } | |
| 66 | |
| 67 namespace data_reduction_proxy { | |
| 68 | |
| 69 // Utility function... | |
| 70 // For a given string, calculate and return the MD5 hash value of the string. | |
| 71 std::string GetMD5(const std::string &input) { | |
|
bengr
2014/07/02 17:30:59
Look at https://code.google.com/p/chromium/codesea
xingx
2014/07/06 03:18:18
We can discuss about this, right now I'm using bas
| |
| 72 base::MD5Context context; | |
| 73 base::MD5Init(&context); | |
| 74 base::MD5Update(&context, input); | |
| 75 base::MD5Digest new_digest; | |
| 76 base::MD5Final(&new_digest, &context); | |
| 77 return std::string((char*)new_digest.a, ARRAYSIZE_UNSAFE(new_digest.a)); | |
| 78 } | |
| 79 | |
| 80 // Utility function... | |
| 81 // For a given |header_name|, get all its values and return the vector contains | |
| 82 // all of the values. | |
| 83 std::vector<std::string> GetHeaderValues( | |
| 84 const net::HttpResponseHeaders* headers, const std::string& header_name) { | |
| 85 std::vector<std::string> values; | |
| 86 std::string value; | |
| 87 void* iter = NULL; | |
| 88 while (headers->EnumerateHeader(&iter, header_name, &value)) { | |
| 89 values.push_back(value); | |
| 90 } | |
| 91 return values; | |
| 92 } | |
| 93 | |
| 94 // Utility function, exposed for unittest. | |
| 95 // For Chrome-Proxy header values |values|, check whether it contains two | |
| 96 // fingerprints: | |
| 97 // |kTamperDetectFingerprintChromeProxy| and |kTamperDetectFingerprint|. | |
| 98 // If not, means that there is no tamper detect request, return false; | |
| 99 // otherwise save these two fingerprints to: | |
| 100 // |chrome_proxy_fingerprint| and |other_fingerprints| | |
| 101 // for later use and return true. | |
| 102 bool ContainsTamperDetectFingerprints(std::vector<std::string>& values, | |
|
bolian
2014/07/02 23:47:37
Don't repeat func doc from the .h file. For expos
xingx
2014/07/06 03:18:18
Done.
| |
| 103 std::string& chrome_proxy_fingerprint, | |
| 104 std::string& other_fingerprints) { | |
| 105 // Enumerate the values of Chrome-Proxy header and check if there is the | |
| 106 // fingerprint of Chrome-Proxy header (kTamperDetectFingerprintChromeProxy) | |
| 107 // and other fingerprints (kTamperDetectFingerprint) | |
| 108 bool contains_tamper_detect_fingerprints = false; | |
| 109 for (size_t i = 0; i < values.size(); ++i) { | |
| 110 if (values[i].find(kTamperDetectFingerprintChromeProxy) == 0) { | |
| 111 contains_tamper_detect_fingerprints = true; | |
| 112 // Save Chrome-Proxy fingerprint. | |
| 113 chrome_proxy_fingerprint = values[i]. | |
| 114 substr(strlen(kTamperDetectFingerprintChromeProxy)); | |
| 115 // Erase Chrome-Proxy's fingerprint from Chrome-Proxy header for | |
| 116 // later fingerprint calculation. | |
| 117 values.erase(values.begin() + (i--)); | |
|
bengr
2014/07/02 17:30:59
Separate out the decrement and explain why it is n
xingx
2014/07/06 03:18:18
Done.
| |
| 118 } | |
| 119 else if (values[i].find(kTamperDetectFingerprint) == 0) | |
|
bengr
2014/07/02 17:30:59
Add curly braces.
xingx
2014/07/06 03:18:18
Done.
| |
| 120 // Save other fingerprints. | |
| 121 other_fingerprints = values[i].substr(strlen(kTamperDetectFingerprint)); | |
| 122 } | |
| 123 return contains_tamper_detect_fingerprints; | |
| 124 } | |
| 125 | |
| 126 // The main function for detecting tamper. | |
| 127 // For such response, the function checks whether there is a tamper detect | |
| 128 // request from data reduction proxy. | |
| 129 // if so, it checks whether there are tampers for each fingerprint one by one | |
| 130 // and report the results to UMA. | |
| 131 void CheckResponseFingerprint(const net::HttpResponseHeaders* headers, | |
| 132 const bool is_secure_scheme) | |
| 133 { | |
| 134 // Get all the values of Chrome-Proxy header. | |
|
bolian
2014/07/02 23:47:37
rm doc here. This is obvious from reading the code
xingx
2014/07/06 03:18:18
Done.
| |
| 135 std::vector<std::string> values = GetHeaderValues(headers, "Chrome-Proxy"); | |
| 136 | |
| 137 // |chrome_proxy_fingerprint| holds the value of fingerprint of | |
| 138 // Chrome-Proxy header. | |
| 139 // |other_fingerprints| holds the value of other fingerprints. | |
| 140 std::string chrome_proxy_fingerprint, other_fingerprints; | |
| 141 | |
| 142 // Check if there are fingerprints (and thus need to detect tamper). | |
| 143 if (!ContainsTamperDetectFingerprints(values, | |
| 144 chrome_proxy_fingerprint, | |
| 145 other_fingerprints)) | |
| 146 return; | |
| 147 | |
| 148 // Found tamper detect request field. | |
| 149 // Get carrier ID. | |
| 150 unsigned mcc_mnc = 0; | |
| 151 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &mcc_mnc); | |
| 152 | |
| 153 // Initialize tamper detect object. | |
|
bolian
2014/07/02 23:47:37
rm. Useless comment.
xingx
2014/07/06 03:18:18
Done.
| |
| 154 DataReductionProxyTamperDetect tamper_detect(headers, is_secure_scheme, | |
| 155 mcc_mnc, &values); | |
| 156 | |
| 157 // Check if Chrome-Proxy header has been tampered. | |
| 158 if (tamper_detect.CheckHeaderChromeProxy(chrome_proxy_fingerprint)) { | |
| 159 UMA_REPORT(is_secure_scheme, | |
| 160 "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy", | |
| 161 "DataReductionProxy.HTTPHeaderTampered_ChromeProxy", | |
| 162 mcc_mnc); | |
| 163 return; | |
| 164 } else | |
| 165 UMA_REPORT(is_secure_scheme, | |
| 166 "DataReductionProxy.HTTPSHeaderTamperDetection", | |
| 167 "DataReductionProxy.HTTPHeaderTamperDetection", | |
| 168 mcc_mnc); | |
| 169 | |
| 170 // Separate fingerprints from |other_fingerprints|. | |
| 171 net::HttpUtil::ValuesIterator it(other_fingerprints.begin(), | |
| 172 other_fingerprints.end(), '|'); | |
| 173 | |
| 174 // For each fingerprint, get its name |key| and the fingerprint value |value| | |
| 175 // from data reduction proxy. CheckReportFingerprint will handle the tamper | |
| 176 // detect and corresponding UMA report. | |
| 177 size_t delimiter_pos = std::string::npos; | |
| 178 while (it.GetNext()) { | |
| 179 delimiter_pos = it.value().find("="); | |
| 180 if (delimiter_pos == std::string::npos) | |
| 181 continue; | |
| 182 std::string key = it.value().substr(0, delimiter_pos); | |
| 183 std::string value = it.value().substr(delimiter_pos + 1); | |
| 184 tamper_detect.CheckReportFingerprint(key, value); | |
| 185 } | |
| 186 return; | |
| 187 } | |
| 188 | |
| 189 // Constructor of DataReductionProxyTamperDetect class. | |
|
bolian
2014/07/02 23:47:37
delete "// Constructor of DataReductionProxyTamper
xingx
2014/07/06 03:18:18
Done.
| |
| 190 // It initialize the function pointer map. | |
| 191 // Right now we have 3 fingerprints to check (besides Chrome-Proxy header's | |
| 192 // fingerprint, which has been handled specially. | |
| 193 // In the future we can add more fingerprints to check, need to implement | |
| 194 // a pair of functions: | |
| 195 // checking and reporting, and then add it to the function map. | |
| 196 DataReductionProxyTamperDetect::DataReductionProxyTamperDetect( | |
| 197 const net::HttpResponseHeaders* headers, const bool secure, | |
| 198 const unsigned mcc_mnc_, std::vector<std::string>* values) | |
| 199 : response_headers(headers), | |
| 200 is_secure_scheme(secure), | |
| 201 mcc_mnc(mcc_mnc_), | |
| 202 clean_chrome_proxy_header_values(values) { | |
| 203 check_report_func_map = std::map<std::string, CheckReportFuncs>(); | |
| 204 | |
| 205 check_report_func_map[kTamperDetectFingerprintVia] = | |
| 206 {&DataReductionProxyTamperDetect::CheckHeaderVia, | |
| 207 &DataReductionProxyTamperDetect::ReportHeaderVia}; | |
| 208 | |
| 209 check_report_func_map[kTamperDetectFingerprintOther] = | |
| 210 {&DataReductionProxyTamperDetect::CheckHeaderOtherHeaders, | |
| 211 &DataReductionProxyTamperDetect::ReportHeaderOtherHeaders}; | |
| 212 | |
| 213 check_report_func_map[kTamperDetectFingerprintContengLength] = | |
| 214 {&DataReductionProxyTamperDetect::CheckHeaderContentLength, | |
| 215 &DataReductionProxyTamperDetect::ReportHeaderContentLength}; | |
| 216 }; | |
| 217 | |
| 218 DataReductionProxyTamperDetect::~DataReductionProxyTamperDetect() {}; | |
| 219 | |
| 220 // For fingerprint name tag |key|, call it's corresponding checking function | |
| 221 // and reporting function. | |
| 222 void DataReductionProxyTamperDetect::CheckReportFingerprint( | |
| 223 const std::string& key, const std::string& fingerprint) { | |
| 224 CheckReportFuncs funcs = check_report_func_map[key]; | |
| 225 if ((this->*funcs.check_tamper_func)(fingerprint)) | |
|
bengr
2014/07/02 17:30:59
use base::Callback
xingx
2014/07/06 03:18:18
Changed to switch / enum.
| |
| 226 (this->*funcs.report_tamper_func)(); | |
| 227 } | |
| 228 | |
| 229 // Check whether Chrome-Proxy header has been tampered. | |
| 230 // |fingerprint| is the fingerprint Chrome received from data reduction proxy, | |
| 231 // which is Base64 encoded. Decode it first. Calculate the hash value of | |
| 232 // Chrome-Proxy header. Note that |clean_chrome_proxy_header_values| holds | |
| 233 // the values of Chrome-Proxy header with its own fingerprint removed, | |
| 234 // so it's the correct values to be used to calculate fingerprint. | |
| 235 // Compare calculated fingerprint to the fingerprint from data reduction proxy | |
| 236 // (the removed value) and see there is tamper detected. | |
| 237 bool DataReductionProxyTamperDetect::CheckHeaderChromeProxy( | |
| 238 const std::string& fingerprint) { | |
| 239 std::string received_fingerprint; | |
| 240 if (!base::Base64Decode(fingerprint, &received_fingerprint)) | |
| 241 return false; | |
| 242 | |
| 243 // Calculate the MD5 hash value of Chrome-Proxy. | |
| 244 std::string actual_fingerprint = GetMD5( | |
| 245 ValuesToSortedString(*clean_chrome_proxy_header_values)); | |
| 246 | |
| 247 // Compare and check if there is tamper detected. | |
| 248 return received_fingerprint != actual_fingerprint; | |
| 249 } | |
| 250 | |
| 251 // For Via header tamper detection... | |
| 252 // Check whether there are proxies/middleboxes between Chrome | |
| 253 // and data reduction proxy. Concretely, it checks whether there are other | |
| 254 // proxies/middleboxes' name after data reduction proxy's name in Via header. | |
| 255 bool DataReductionProxyTamperDetect::CheckHeaderVia( | |
| 256 const std::string& fingerprint) { | |
| 257 | |
| 258 std::vector<std::string> vias = GetHeaderValues(response_headers, "via"); | |
| 259 | |
| 260 // If there is no tag, then data reduction proxy's tag have been removed. | |
| 261 if (vias.size() == 0) return true; | |
| 262 // Check whether the last proxy/middlebox is data reduction proxy or not. | |
| 263 return vias[vias.size() - 1]. | |
| 264 find(kDataReductionProxyViaValue) == std::string::npos; | |
| 265 } | |
| 266 | |
| 267 // Report Via header tamper detected. | |
| 268 void DataReductionProxyTamperDetect::ReportHeaderVia() { | |
| 269 UMA_REPORT(is_secure_scheme, | |
| 270 "DataReductionProxy.HTTPSHeaderTampered_Via", | |
| 271 "DataReductionProxy.HTTPHeaderTampered_Via", | |
| 272 mcc_mnc); | |
| 273 } | |
| 274 | |
| 275 | |
| 276 // For other headers tamper detection... | |
| 277 // Check whether values of a predefined list of headers have been tampered. | |
| 278 // The format for |fingerprint| is: | |
| 279 // [base64fingerprint]:header_name1:header_namer2:... | |
| 280 // Firstly extract the header names in the |fingerprint|. | |
| 281 // For each header, | |
| 282 // 1) we get all the values of such header; | |
| 283 // 2) we sort the values alphabetically; | |
| 284 // 3) we concatenate sorted values to a string and calculate MD5 hash on it. | |
| 285 // Finally, we compare whether it equals to the fingerprint from | |
| 286 // data reduction proxy. | |
| 287 bool DataReductionProxyTamperDetect::CheckHeaderOtherHeaders( | |
| 288 const std::string& fingerprint) { | |
|
bengr
2014/07/02 17:30:59
indent 4.
xingx
2014/07/06 03:18:18
Done.
| |
| 289 std::string received_fingerprint; | |
| 290 | |
| 291 net::HttpUtil::ValuesIterator it(fingerprint.begin(), | |
| 292 fingerprint.end(), ':'); | |
| 293 | |
| 294 // Make sure there is [base64fingerprint] and it can be decoded. | |
| 295 if (!(it.GetNext() && | |
| 296 base::Base64Decode(std::string(it.value()), &received_fingerprint))) | |
| 297 return false; | |
| 298 | |
| 299 std::string header_values; | |
| 300 // Enumerate the list of headers. | |
| 301 while (it.GetNext()) { | |
| 302 // Get values of one header. | |
| 303 std::vector<std::string> values = GetHeaderValues(response_headers, | |
|
bengr
2014/07/02 17:30:59
move GetHeaderValues to next line and indent 4.
xingx
2014/07/06 03:18:18
Done.
| |
| 304 std::string(it.value())); | |
| 305 // Sort the values and concatenate them. | |
| 306 header_values += ValuesToSortedString(values) + ";"; | |
| 307 } | |
| 308 | |
| 309 // Calculate MD5 hash value on the concatenated string. | |
| 310 std::string actual_fingerprint = GetMD5(header_values); | |
| 311 | |
| 312 return received_fingerprint != actual_fingerprint; | |
| 313 } | |
| 314 | |
| 315 // Report other headers tamper detected. | |
| 316 void DataReductionProxyTamperDetect::ReportHeaderOtherHeaders() { | |
| 317 UMA_REPORT(is_secure_scheme, | |
| 318 "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders", | |
| 319 "DataReductionProxy.HTTPHeaderTampered_OtherHeaders", | |
| 320 mcc_mnc); | |
| 321 } | |
| 322 | |
| 323 | |
| 324 // For Content-Length tamper detection... | |
| 325 // Check whether the Content-Length value is different from what | |
| 326 // data reduction proxy sees. This is an indicator that the response body | |
| 327 // have been modified. | |
| 328 // It's modified only if we can decode Content-Length numbers at both end | |
| 329 // and such two numbers are not equal. | |
| 330 bool DataReductionProxyTamperDetect::CheckHeaderContentLength( | |
| 331 const std::string& fingerprint) { | |
| 332 int received_content_length, actual_content_length; | |
| 333 // If Content-Length value from data reduction proxy is not available or | |
| 334 // it cannot be converted to integer, pass. | |
| 335 if (base::StringToInt(fingerprint, &received_content_length)) { | |
| 336 std::string actual_content_length_; | |
| 337 // If there is Content-Length at Chrome client side is not available, pass. | |
| 338 if (response_headers->GetNormalizedHeader("Content-Length", | |
| 339 &actual_content_length_)) { | |
| 340 // If the Content-Length value cannot be converted to integer, | |
| 341 // i.e., not valid, pass. | |
| 342 if (!base::StringToInt(actual_content_length_, &actual_content_length)) | |
| 343 return false; | |
| 344 return received_content_length != actual_content_length; | |
| 345 } | |
| 346 } | |
| 347 else | |
| 348 { | |
| 349 LOG(WARNING) << "xing can't convert"; | |
|
bengr
2014/07/02 17:30:59
remove.
xingx
2014/07/06 03:18:18
Done.
| |
| 350 } | |
| 351 return false; | |
| 352 } | |
| 353 | |
| 354 // Report Content-Length tamper detected. | |
| 355 // Get MIME type of the response and report to different UMA histogram. | |
| 356 // Right now MIME types contain JavaScript, CSS, Images, and others. | |
| 357 void DataReductionProxyTamperDetect::ReportHeaderContentLength() { | |
| 358 std::string mime_type; | |
| 359 // Get MIME type. | |
| 360 response_headers->GetMimeType(&mime_type); | |
| 361 UMA_REPORT(is_secure_scheme, | |
| 362 "DataReductionProxy.HTTPSHeaderTampered_ContentLength", | |
| 363 "DataReductionProxy.HTTPHeaderTampered_ContentLength", | |
| 364 mcc_mnc); | |
| 365 | |
| 366 // Report tampered JavaScript. | |
| 367 if (mime_type.compare("text/javascript") == 0 || | |
| 368 mime_type.compare("application/x-javascript") == 0 || | |
| 369 mime_type.compare("application/javascript") == 0) | |
| 370 UMA_REPORT(is_secure_scheme, | |
| 371 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS", | |
| 372 "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS", | |
| 373 mcc_mnc); | |
| 374 // Report tampered CSSs. | |
| 375 else if (mime_type.compare("text/css") == 0) | |
| 376 UMA_REPORT(is_secure_scheme, | |
| 377 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS", | |
| 378 "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS", | |
| 379 mcc_mnc); | |
| 380 // Report tampered images. | |
| 381 else if (mime_type.find("image") == 0) | |
| 382 UMA_REPORT(is_secure_scheme, | |
| 383 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image", | |
| 384 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image", | |
| 385 mcc_mnc); | |
| 386 // Report tampered other MIME types. | |
| 387 else | |
| 388 UMA_REPORT(is_secure_scheme, | |
| 389 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other", | |
| 390 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other", | |
| 391 mcc_mnc); | |
| 392 } | |
| 393 | |
| 394 | |
|
bengr
2014/07/02 17:30:59
remove blank line.
xingx
2014/07/06 03:18:18
Done.
| |
| 395 } // namespace data_reduction_proxy | |
| OLD | NEW |