Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(125)

Side by Side Diff: components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detect.cc

Issue 338483002: Chrome Participated Tamper Detect (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Move modification on _headers.h/cc to another CL. Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_de tect.h"
6
7 #include <algorithm>
8 #include <cstring>
9
10 #include "base/base64.h"
11 #include "base/md5.h"
12 #include "base/metrics/histogram.h"
13 #include "base/metrics/sparse_histogram.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
16 #include "net/android/network_library.h"
17 #include "net/http/http_response_headers.h"
18 #include "net/http/http_util.h"
19
20 // Macro for UMA reporting. First reporting to either |https_histogram| or
21 // |http_histogram| depending on |scheme_is_https|, with |carrier_id| as bucket
22 // which counts for each carrier, how many responses have been tampered with.
23 // Then reporting to |http(s)_histogram|_Total, which counts the total number of
24 // detected tampering (sum of tampering detected for all carriers).
25 #define UMA_REPORT(scheme_is_https, http_histogram, https_histogram, carrier_id) \
26 do { \
27 if (scheme_is_https) { \
28 UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, carrier_id); \
29 UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
30 } else { \
31 UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, carrier_id); \
32 UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
33 }\
34 } while (0)
35
36 namespace {
37 // Four fingerprints will be added to Chrome-Proxy header. One starts with
38 // |kTamperDetectFingerprintChromeProxy|, which is the fingerprint for the
39 // Chrome-Proxy header. The other three have been put together into a string
40 // starts with key tag |kTamperDetectFingerprint|.
41 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.
42 const char kTamperDetectFingerprintChromeProxy[] = "cp";
43
44 // Fingerprint |kTamperDetectFingerprint| contains three fingerprints, each
45 // starts with a key tag followed by "=" and its fingerprint value. Three
46 // fingerprints and their respective tags are defined below.
47 const char kTamperDetectFingerprintVia[] = "via";
48 const char kTamperDetectFingerprintOther[] = "oh";
49 const char kTamperDetectFingerprintContengLength[] = "cl";
50 } // namespace
bengr 2014/07/16 21:24:48 Add a space before //
xingx 2014/07/18 17:25:01 Done.
51
52 namespace data_reduction_proxy {
53
54 // static
55 void DataReductionProxyTamperDetection::CheckResponseFingerprint(
56 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.
57 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
58 {
bengr 2014/07/16 21:24:48 Move the curly up a line.
xingx 2014/07/18 17:25:01 Done.
59 // 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.
60 if (!GetDataReductionProxyActionValue(
61 headers, kTamperDetectFingerprintChromeProxy, NULL))
62 return;
63
64 // Found tamper detection request field. Saves Chrome-Proxy header values to
65 // get fingerprints.
66 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.
67
68 // |chrome_proxy_fingerprint| holds the value of fingerprint of Chrome-Proxy
69 // header. |other_fingerprints| holds the value of other fingerprints.
70 // |values| saves all the values of Chrome-Proxy header but with fingerprint
71 // for Chrome-Proxy header removed.
72 std::string chrome_proxy_fingerprint, other_fingerprints;
73 if (!GetTamperDetectionFingerprints(&values,
74 &chrome_proxy_fingerprint,
75 &other_fingerprints))
76 return;
bengr 2014/07/16 21:24:48 I would add curly braces around this.
xingx 2014/07/18 17:25:01 Done.
77
78 // Get carrier ID.
79 unsigned carrier_id = 0;
80 #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
81 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &carrier_id);
82 #endif
83
84 DataReductionProxyTamperDetection tamper_detection(headers,
85 is_secure_scheme,
86 carrier_id,
87 &values);
88
89 // Check if Chrome-Proxy header has been tampered with.
90 if (tamper_detection.IsChromeProxyHeaderTampered(chrome_proxy_fingerprint)) {
91 tamper_detection.ReportChromeProxyHeaderTamperedUMA();
92 return;
93 } else {
94 UMA_REPORT(is_secure_scheme,
95 "DataReductionProxy.HTTPSHeaderTamperDetection",
96 "DataReductionProxy.HTTPHeaderTamperDetection",
97 carrier_id);
98 }
99
100 // Separate fingerprints from |other_fingerprints|, with delimiter "|".
101 net::HttpUtil::ValuesIterator it(
102 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.
103
104 // 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.
105 size_t delimiter_pos = std::string::npos;
106 while (it.GetNext()) {
107 delimiter_pos = it.value().find("=");
108 if (delimiter_pos == std::string::npos)
109 continue;
110 std::string key = it.value().substr(0, delimiter_pos);
111 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.
112
113 FingerprintCode fingerprint_code = tamper_detection.GetFingerprintCode(key);
114 switch (fingerprint_code) {
115 case VIA:
116 if (tamper_detection.IsViaHeaderTampered(value))
117 tamper_detection.ReportViaHeaderTamperedUMA();
118 break;
119 case OTHERHEADERS:
120 if (tamper_detection.AreOtherHeadersTampered(value))
121 tamper_detection.ReportOtherHeadersTamperedUMA();
122 break;
123 case CONTENTLENGTH:
124 if (tamper_detection.IsContentLengthHeaderTampered(value))
125 tamper_detection.ReportContentLengthHeaderTamperedUMA();
126 break;
127 default:
128 NOTREACHED();
129 break;
130 }
131 }
132 return;
133 }
134
135 // Constructor initializes fingerprint code map.
136 DataReductionProxyTamperDetection::DataReductionProxyTamperDetection(
137 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.
138 const bool is_secure,
139 const unsigned carrier_id,
140 std::vector<std::string>* values)
141 : response_headers_(headers),
142 is_secure_scheme_(is_secure),
143 carrier_id_(carrier_id),
144 clean_chrome_proxy_header_values_(values) {
145 fingperprint_tag_code_map_ = std::map<std::string, FingerprintCode>();
146 fingperprint_tag_code_map_[kTamperDetectFingerprintVia] = VIA;
147 fingperprint_tag_code_map_[kTamperDetectFingerprintOther] = OTHERHEADERS;
148 fingperprint_tag_code_map_[kTamperDetectFingerprintContengLength] =
149 CONTENTLENGTH;
150 };
151
152 DataReductionProxyTamperDetection::~DataReductionProxyTamperDetection() {};
153
154 // Checks whether the Chrome-Proxy header has been tampered with. |fingerprint|
155 // is the fingerprint received from data reduction proxy, which is Base64
156 // encoded. Decodes it first. Calculates the hash value of Chrome-Proxy header.
157 // Note that |clean_chrome_proxy_header_values_| holds the values of
158 // Chrome-Proxy header with its own fingerprint removed, so it's the correct
159 // values to be used to calculate fingerprint. Compares calculated fingerprint
160 // to the fingerprint from data reduction proxy (the removed value) and see
161 // there is tamper detected or not.
162 bool DataReductionProxyTamperDetection::IsChromeProxyHeaderTampered(
163 const std::string& fingerprint) const {
164 std::string received_fingerprint;
165 if (!base::Base64Decode(fingerprint, &received_fingerprint))
166 return true;
167 // Calculate the MD5 hash value of Chrome-Proxy.
168 std::string actual_fingerprint = GetMD5(
169 ValuesToSortedString(clean_chrome_proxy_header_values_));
170
171 return received_fingerprint != actual_fingerprint;
172 }
173
174 // Reports Chrome-Proxy header tamper detected.
175 void DataReductionProxyTamperDetection::ReportChromeProxyHeaderTamperedUMA()
176 const {
177 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.
178 "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy",
179 "DataReductionProxy.HTTPHeaderTampered_ChromeProxy",
180 carrier_id_);
181 }
182
183 // Checks whether there are other proxies/middleboxes' name after the data
184 // reduction proxy's name in Via header.
185 bool DataReductionProxyTamperDetection::IsViaHeaderTampered(
186 const std::string& fingerprint) const {
187 bool has, is_the_last;
188 has = HasDataReductionProxyViaHeader(response_headers_, &is_the_last);
189
190 // 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.
191 if (!has) {
192 UMA_REPORT(is_secure_scheme_,
193 "DataReductionProxy.HTTPSHeaderTampered_Via_Missing",
194 "DataReductionProxy.HTTPHeaderTampered_Via_Missing",
195 carrier_id_);
196 return false;
197 }
198
199 return !is_the_last;
200 }
201
202 // Reports Via header tamper detected.
203 void DataReductionProxyTamperDetection::ReportViaHeaderTamperedUMA() const {
204 UMA_REPORT(is_secure_scheme_,
205 "DataReductionProxy.HTTPSHeaderTampered_Via",
206 "DataReductionProxy.HTTPHeaderTampered_Via",
207 carrier_id_);
208 }
209
210 // Checks whether values of a predefined list of headers have been tampered.
211 // The format for |fingerprint| is:
212 // [base64encoded_fingerprint]:header_name1:header_namer2:...
213 // Firstly extract the header names in the |fingerprint|. For each header, get
214 // all the values of such header, sort the values and concatenate them to a
215 // string. Concatenate the string for all the headers (with delimiter ";") and
216 // calculate the MD5 hash value on it. Compare such hash value to the
217 // fingerprint received from data reduction proxy.
218 bool DataReductionProxyTamperDetection::AreOtherHeadersTampered(
219 const std::string& fingerprint) const {
220 std::string received_fingerprint;
221
222 // Fingerprint should never be empty.
223 DCHECK(fingerprint.size());
224
225 net::HttpUtil::ValuesIterator it(fingerprint.begin(),
226 fingerprint.end(), ':');
227
228 // The first value from fingerprint is the actual fingerprint; the following
229 // values are the header names will be included for fingerprint calculation.
230 // Make sure there is [base64fingerprint] and it can be decoded.
231 if (!(it.GetNext() &&
232 base::Base64Decode(it.value(), &received_fingerprint))) {
233 NOTREACHED();
234 return true;
235 }
236
237 std::string header_values;
238 // Enumerate the list of headers.
239 while (it.GetNext()) {
240 // Get values of one header.
241 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.
242 GetHeaderValues(response_headers_, it.value());
243 // Sort the values and concatenate them.
244 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.
245 }
246
247 // Calculate MD5 hash value on the concatenated string.
248 std::string actual_fingerprint = GetMD5(header_values);
249
250 return received_fingerprint != actual_fingerprint;
251 }
252
253 // Reports other headers tamper detected.
254 void DataReductionProxyTamperDetection::ReportOtherHeadersTamperedUMA() const {
255 UMA_REPORT(is_secure_scheme_,
256 "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders",
257 "DataReductionProxy.HTTPHeaderTampered_OtherHeaders",
258 carrier_id_);
259 }
260
261 // Checks whether the Content-Length value is different from what data reduction
262 // proxy sees. Reports it as modified only if Content-Length can be decoded as
263 // an interger at both end and such two numbers are not equal.
264 bool DataReductionProxyTamperDetection::IsContentLengthHeaderTampered(
265 const std::string& fingerprint) const {
266 int received_content_length, actual_content_length;
267 // If Content-Length value from data reduction proxy is not available or
268 // it cannot be converted to an integer, pass.
269 if (base::StringToInt(fingerprint, &received_content_length)) {
270 std::string actual_content_length_;
271 // If there is no Content-Length header received, pass.
272 if (response_headers_->GetNormalizedHeader("Content-Length",
273 &actual_content_length_)) {
bengr 2014/07/16 21:24:47 indentation.
xingx 2014/07/18 17:25:02 Done.
274 // If the Content-Length value cannot be converted to integer,
275 // i.e., not valid, pass.
276 if (!base::StringToInt(actual_content_length_, &actual_content_length))
277 return false;
278 return received_content_length != actual_content_length;
279 }
280 }
281 return false;
282 }
283
284 // Reports Content-Length tamper detected.
285 // Gets MIME type of the response and report to different UMA histogram.
286 // Right now MIME types contain JavaScript, CSS, Images, and others.
287 void DataReductionProxyTamperDetection::ReportContentLengthHeaderTamperedUMA()
288 const {
289 UMA_REPORT(is_secure_scheme_,
290 "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
291 "DataReductionProxy.HTTPHeaderTampered_ContentLength",
292 carrier_id_);
293
294 // Get MIME type.
295 std::string mime_type;
296 response_headers_->GetMimeType(&mime_type);
297
298 // Reports tampered JavaScript.
299 if (mime_type.compare("text/javascript") == 0 ||
300 mime_type.compare("application/x-javascript") == 0 ||
301 mime_type.compare("application/javascript") == 0)
302 UMA_REPORT(is_secure_scheme_,
303 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS",
304 "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS",
305 carrier_id_);
306 // Reports tampered CSSs.
307 else if (mime_type.compare("text/css") == 0)
308 UMA_REPORT(is_secure_scheme_,
309 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS",
310 "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS",
311 carrier_id_);
312 // Reports tampered images.
313 else if (mime_type.find("image") == 0)
314 UMA_REPORT(is_secure_scheme_,
315 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image",
316 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image",
317 carrier_id_);
318 // Reports tampered other MIME types.
319 else
320 UMA_REPORT(is_secure_scheme_,
321 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other",
322 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other",
323 carrier_id_);
324 }
325
326 DataReductionProxyTamperDetection::FingerprintCode
327 DataReductionProxyTamperDetection::GetFingerprintCode(
328 const std::string& tag) {
329 std::map<std::string, DataReductionProxyTamperDetection::FingerprintCode>
330 ::iterator it = fingperprint_tag_code_map_.find(tag);
331
332 return it == fingperprint_tag_code_map_.end() ? NONEXIST : it->second;
333 }
334
335 // 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.
336 // fingerprints for Chrome-Proxy header (kTamperDetectFingerprintChromeProxy)
337 // and other fingerprints (kTamperDetectFingerprint). If there are, saves them
338 // accordingly, removes fingerprint for Chrome-Proxy header from |values| and
339 // return true, otherwise return false.
340 bool DataReductionProxyTamperDetection::GetTamperDetectionFingerprints(
341 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.
342 std::string* chrome_proxy_fingerprint,
343 std::string* other_fingerprints) {
344 int chrome_proxy_fingerprint_index = -1;
345
346 size_t size = values->size();
347 for (size_t i = 0; i < size; ++i) {
348 if ((*values)[i].find(kTamperDetectFingerprintChromeProxy) == 0 &&
349 (*values)[i][strlen(kTamperDetectFingerprintChromeProxy)] == '=') {
350 chrome_proxy_fingerprint_index = i;
351 // Saves Chrome-Proxy fingerprint.
352 *chrome_proxy_fingerprint = (*values)[i].substr(strlen(
353 kTamperDetectFingerprintChromeProxy));
354 }
355 else if ((*values)[i].find(kTamperDetectFingerprints) == 0 &&
356 (*values)[i][strlen(kTamperDetectFingerprints)] == '=') {
357 // Saves other fingerprints.
358 *other_fingerprints = (*values)[i].substr(
359 strlen(kTamperDetectFingerprints));
360 }
361 }
362
363 if (chrome_proxy_fingerprint_index == -1)
364 return false;
365
366 // Erases Chrome-Proxy's fingerprint from Chrome-Proxy header for
367 // later fingerprint calculation.
368 values->erase(values->begin() + chrome_proxy_fingerprint_index);
369 return true;
370 }
371
372 // Sorts the strings in |values| and concatenates them into a string.
373 std::string DataReductionProxyTamperDetection::ValuesToSortedString(
374 std::vector<std::string>* values) {
375 std::string aggregated_values;
376
377 std::sort(values->begin(), values->end());
378 for (size_t i = 0; i < values->size(); ++i)
379 aggregated_values += (*values)[i] + ",";
380 return aggregated_values;
381 }
382
383 // For a given string, calculates and returns the MD5 hash value of the string.
384 std::string DataReductionProxyTamperDetection::GetMD5(
385 const std::string &input) {
386 base::MD5Digest digest;
387 base::MD5Sum(input.c_str(), input.size(), &digest);
388 return std::string((char*)digest.a, ARRAYSIZE_UNSAFE(digest.a));
389 }
390
391 // For a given |header_name|, gets all its values and returns them as a vector.
392 std::vector<std::string> DataReductionProxyTamperDetection::GetHeaderValues(
393 const net::HttpResponseHeaders* headers, const std::string& header_name) {
394 std::vector<std::string> values;
395 std::string value;
396 void* iter = NULL;
397 while (headers->EnumerateHeader(&iter, header_name, &value)) {
398 values.push_back(value);
399 }
400 return values;
401 }
402 } // namespace data_reduction_proxy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698