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

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: Created 6 years, 4 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/http/http_response_headers.h"
17 #include "net/http/http_util.h"
18
19 #if defined(OS_ANDROID)
20 #include "net/android/network_library.h"
21 #endif
22
23 // Macro for UMA reporting. Depending on |scheme_is_https|, it first reports to
bengr 2014/07/28 21:56:47 I can't parse "it first reports to histograms even
xingx1 2014/07/30 03:44:18 Changed. Two things: 1) report to |http_histogra
24 // histogram events |https_histogram| or |http_histogram| by |carrier_id|; then
25 // reports total counts to |https_histogram|_Total or |http_histogram|_Total.
26 #define REPORT_TAMPER_DETECTION_UMA(scheme_is_https, http_histogram, https_histo gram, carrier_id) \
27 do { \
28 if (scheme_is_https) { \
29 UMA_HISTOGRAM_SPARSE_SLOWLY(https_histogram, carrier_id); \
30 UMA_HISTOGRAM_COUNTS(https_histogram "_Total", 1); \
31 } else { \
32 UMA_HISTOGRAM_SPARSE_SLOWLY(http_histogram, carrier_id); \
33 UMA_HISTOGRAM_COUNTS(http_histogram "_Total", 1); \
34 }\
35 } while (0)
36
37 namespace data_reduction_proxy {
38
39 // static
40 bool DataReductionProxyTamperDetection::DetectAndReport(
41 const net::HttpResponseHeaders* headers,
42 const bool is_secure_scheme) {
bengr 2014/07/28 21:56:48 scheme_is_https
xingx1 2014/07/30 03:44:18 Done. Also with .h file
43 DCHECK(headers);
bengr 2014/07/28 21:56:48 are headers ever NULL? When does that happen?
xingx1 2014/07/30 03:44:18 It should not happen. Done.
44 if (!headers)
45 return false;
46
47 // If the fingerprint of the Chrome-Proxy header is absent, abort tamper
bengr 2014/07/28 21:56:49 So a middlebox that strips fingerprints won't be d
xingx1 2014/07/30 03:44:19 Done.
48 // detection.
49 std::string chrome_proxy_fingerprint;
50 if (!GetDataReductionProxyActionValue(
51 headers,
52 kChromeProxyActionFingerprintChromeProxy,
53 &chrome_proxy_fingerprint))
54 return false;
55
56 // Gets the Chrome-Proxy header values.
57 std::vector<std::string> chrome_proxy_header_values =
58 GetHeaderValues(headers, "Chrome-Proxy");
59
60 // Removes header's fingerprint for generating the fingerprint of received
61 // Chrome-Proxy header later.
62 RemoveChromeProxyFingerprint(&chrome_proxy_header_values);
63
64 // Get carrier ID.
65 unsigned carrier_id = 0;
66 #if defined(OS_ANDROID)
67 base::StringToUint(net::android::GetTelephonyNetworkOperator(), &carrier_id);
68 #endif
69
70 DataReductionProxyTamperDetection tamper_detection(
71 headers,
72 is_secure_scheme,
73 carrier_id,
74 &chrome_proxy_header_values);
75
76 // Checks if the Chrome-Proxy header has been tampered with.
77 if (tamper_detection.IsChromeProxyHeaderTampered(chrome_proxy_fingerprint)) {
78 tamper_detection.ReportChromeProxyHeaderTamperedUMA();
79 return true;
80 }
81
82 // Since the Chrome-Proxy header has not been tampered with, reports the
83 // number of responses that other fingerprints will be checked.
bengr 2014/07/28 21:56:47 I don't understand. Please clarify.
xingx1 2014/07/30 03:44:17 Changed.
84 REPORT_TAMPER_DETECTION_UMA(
85 is_secure_scheme,
86 "DataReductionProxy.HTTPSHeaderTamperDetection",
87 "DataReductionProxy.HTTPHeaderTamperDetection",
88 carrier_id);
89
90 bool tampered = false;
91 std::map<std::string, FingerprintCode>::iterator i;
92 for (i = tamper_detection.fingerprint_name_code_map_.begin();
93 i != tamper_detection.fingerprint_name_code_map_.end(); ++i) {
94 std::string fingerprint;
95 if (!GetDataReductionProxyActionValue(
96 headers, i->first, &fingerprint)) {
97 continue;
98 }
99
100 switch (i->second) {
101 case VIA:
102 bool has_chrome_proxy_via_header;
103 if (tamper_detection.IsViaHeaderTampered(
104 fingerprint, &has_chrome_proxy_via_header)) {
105 tamper_detection.ReportViaHeaderTamperedUMA(
106 has_chrome_proxy_via_header);
107 tampered = true;
108 }
109 break;
110 case OTHERHEADERS:
111 if (tamper_detection.AreOtherHeadersTampered(fingerprint)) {
112 tamper_detection.ReportOtherHeadersTamperedUMA();
113 tampered = true;
114 }
115 break;
116 case CONTENTLENGTH:
117 if (tamper_detection.IsContentLengthHeaderTampered(fingerprint)) {
118 tamper_detection.ReportContentLengthHeaderTamperedUMA();
119 tampered = true;
120 }
121 break;
122 default:
123 NOTREACHED();
124 break;
125 }
126 }
127 return tampered;
bengr 2014/07/28 21:56:48 might be better to return a status than a bool.
xingx1 2014/07/30 03:44:19 Acknowledged.
128 }
129
130 // Constructor initializes the map of fingerprint name to code.
bengr 2014/07/28 21:56:49 names to codes
xingx1 2014/07/30 03:44:18 Done.
131 DataReductionProxyTamperDetection::DataReductionProxyTamperDetection(
132 const net::HttpResponseHeaders* headers,
133 const bool is_secure,
bengr 2014/07/28 21:56:47 scheme_is_https
xingx1 2014/07/30 03:44:18 Done.
134 const unsigned carrier_id,
135 std::vector<std::string>* values)
136 : response_headers_(headers),
137 is_secure_scheme_(is_secure),
138 carrier_id_(carrier_id),
139 clean_chrome_proxy_header_values_(values) {
140 DCHECK(headers);
141 fingerprint_name_code_map_ = std::map<std::string, FingerprintCode>();
bengr 2014/07/28 21:56:47 Why do you need this?
xingx1 2014/07/30 03:44:17 Previously need this for "SWITCH", now I changed t
142 fingerprint_name_code_map_
143 [kChromeProxyActionFingerprintVia] = VIA;
144 fingerprint_name_code_map_
145 [kChromeProxyActionFingerprintOtherHeaders] = OTHERHEADERS;
146 fingerprint_name_code_map_
147 [kChromeProxyActionFingerprintContentLength] = CONTENTLENGTH;
148 };
149
150 DataReductionProxyTamperDetection::~DataReductionProxyTamperDetection() {};
151
152 // Checks whether the Chrome-Proxy header has been tampered with. |fingerprint|
153 // is the fingerprint received from the data reduction proxy, which is Base64
154 // encoded. Decodes it first. Then calculates the fingerprint of received
155 // Chrome-Proxy header, and compares the two to see whether they are equal or
156 // not. Note that |clean_chrome_proxy_header_values_| holds the values of the
157 // Chrome-Proxy header with its own fingerprint removed, so it's the correct
158 // values to calculate fingerprint of received Chrome-Proxy header.
159 bool DataReductionProxyTamperDetection::IsChromeProxyHeaderTampered(
160 const std::string& fingerprint) const {
161 std::string received_fingerprint;
162 if (!base::Base64Decode(fingerprint, &received_fingerprint))
163 return true;
164 // Calculates the MD5 hash value of Chrome-Proxy.
165 std::string actual_fingerprint = GetMD5(
166 ValuesToSortedString(clean_chrome_proxy_header_values_));
167
168 return received_fingerprint != actual_fingerprint;
169 }
170
171 void DataReductionProxyTamperDetection::ReportChromeProxyHeaderTamperedUMA()
172 const {
173 REPORT_TAMPER_DETECTION_UMA(
174 is_secure_scheme_,
175 "DataReductionProxy.HTTPSHeaderTampered_ChromeProxy",
176 "DataReductionProxy.HTTPHeaderTampered_ChromeProxy",
177 carrier_id_);
178 }
179
180 // Checks whether there are other proxies/middleboxes' name after the data
bengr 2014/07/28 21:56:47 name -> named
xingx1 2014/07/30 03:44:17 Done.
181 // reduction proxy's name in Via header. |has_chrome_proxy_via_header| marks
182 // that whether the data reduction proxy's Via header occurs or not.
183 bool DataReductionProxyTamperDetection::IsViaHeaderTampered(
184 const std::string& fingerprint, bool* has_chrome_proxy_via_header) const {
185 bool has_intermediary;
186 *has_chrome_proxy_via_header = HasDataReductionProxyViaHeader(
187 response_headers_,
188 &has_intermediary);
189
190 if (*has_chrome_proxy_via_header)
191 return !has_intermediary;
192 return false;
193 }
194
195 void DataReductionProxyTamperDetection::ReportViaHeaderTamperedUMA(
196 bool has_chrome_proxy) const {
197 // The Via header of the data reduction proxy is missing.
198 if (!has_chrome_proxy) {
199 REPORT_TAMPER_DETECTION_UMA(
200 is_secure_scheme_,
201 "DataReductionProxy.HTTPSHeaderTampered_Via_Missing",
202 "DataReductionProxy.HTTPHeaderTampered_Via_Missing",
203 carrier_id_);
204 return;
205 }
206
207 REPORT_TAMPER_DETECTION_UMA(
208 is_secure_scheme_,
209 "DataReductionProxy.HTTPSHeaderTampered_Via",
210 "DataReductionProxy.HTTPHeaderTampered_Via",
211 carrier_id_);
212 }
213
214 // Checks whether values of a predefined list of headers have been modified. At
215 // the data reduction proxy side, it constructs a canonical representation of
216 // values of a list headers. The fingerprint is constructed as follows:
bengr 2014/07/28 21:56:47 list headers -> list of headers
xingx1 2014/07/30 03:44:18 Done.
217 // 1) for each header, gets the string representation of its values (same to
bengr 2014/07/28 21:56:49 same to -> same as OR "using" if that is true
xingx1 2014/07/30 03:44:17 Done. I'm explaining fingerprint generation, which
218 // ValuesToSortedString);
219 // 2) concatenates all header's string representation with a ";" delimiter,
bengr 2014/07/28 21:56:48 representation -> representations with a ';' delim
xingx1 2014/07/30 03:44:17 Done.
220 // respect to the order of the header list;
bengr 2014/07/28 21:56:48 what does this mean? "keeping the headers in the o
xingx1 2014/07/30 03:44:19 I mean the order of that header list. Header list
221 // 3) calculates the MD5 hash value of above concatenated string;
222 // 4) appends the header names to the fingerprint, with a delimiter "|".
223 // The constructed fingerprint looks like:
224 // [hashed_fingerprint]|header_name1|header_namer2:...
225 //
226 // To check whether such fingerprint matches the response that the Chromium
bengr 2014/07/28 21:56:49 such a fingerprint
xingx1 2014/07/30 03:44:17 Done.
227 // client receives, the Chromium client firstly extracts the header names. For
bengr 2014/07/28 21:56:47 the Chromium client -> the client
xingx1 2014/07/30 03:44:19 Done.
228 // each header, gets its string representation (by ValuesToSortedString),
229 // concatenates them and calculates the MD5 hash value. Compares such hash
bengr 2014/07/28 21:56:47 such --> the
xingx1 2014/07/30 03:44:18 Done.
230 // value to the fingerprint received from the data reduction proxy.
231 bool DataReductionProxyTamperDetection::AreOtherHeadersTampered(
bengr 2014/07/28 21:56:48 ValidateOtherHeaders
xingx1 2014/07/30 03:44:17 Done.
232 const std::string& fingerprint) const {
233 std::string received_fingerprint;
234 DCHECK(fingerprint.size());
bengr 2014/07/28 21:56:48 DCHECK(!fingerprint.empty());
xingx1 2014/07/30 03:44:19 Done.
235
236 // "|" delimiter would not occur in base64 as well as header names.
237 net::HttpUtil::ValuesIterator it(fingerprint.begin(),
238 fingerprint.end(), '|');
bengr 2014/07/28 21:56:48 can you fit all of these params on one line? E.g.,
xingx1 2014/07/30 03:44:18 Done.
239
240 // The first value from fingerprint is the base64 encoded fingerprint; the
241 // following values are the header names included in fingerprint calculation.
bengr 2014/07/28 21:56:47 in -> in the
xingx1 2014/07/30 03:44:18 Done.
242 // Make sure there is [base64fingerprint] and it can be decoded.
bengr 2014/07/28 21:56:47 Add a note that you ignore the first value.
xingx1 2014/07/30 03:44:18 Done.
243 if (!(it.GetNext() &&
244 base::Base64Decode(it.value(), &received_fingerprint))) {
245 NOTREACHED();
246 return true;
247 }
248
249 std::string header_values;
250 // Enumerates the list of headers.
251 while (it.GetNext()) {
252 // Gets values of one header.
253 std::vector<std::string> response_header_values =
254 GetHeaderValues(response_headers_, it.value());
255 // Sorts the values and concatenate them, with delimiter ";". ";" would not
256 // occur in header values,
257 header_values += ValuesToSortedString(&response_header_values) + ";";
bengr 2014/07/28 21:56:48 Why not have ValuesToSortedSTring add the ";" to t
xingx1 2014/07/30 03:44:18 ValuesToSortedString generates a representation of
258 }
259
260 // Calculates the MD5 hash of the concatenated string.
261 std::string actual_fingerprint = GetMD5(header_values);
bengr 2014/07/28 21:56:47 you could avoid a copy by passing a pointer to the
xingx1 2014/07/30 03:44:17 Done.
262
263 return received_fingerprint != actual_fingerprint;
264 }
265
266 void DataReductionProxyTamperDetection::ReportOtherHeadersTamperedUMA() const {
267 REPORT_TAMPER_DETECTION_UMA(
268 is_secure_scheme_,
269 "DataReductionProxy.HTTPSHeaderTampered_OtherHeaders",
270 "DataReductionProxy.HTTPHeaderTampered_OtherHeaders",
271 carrier_id_);
272 }
273
274 // Checks whether the Content-Length value is different from what the data
275 // reduction proxy sends. Reports it as modified only if Content-Length can be
276 // decoded as an integer at both ends and such two numbers are not equal.
bengr 2014/07/28 21:56:48 I don't understand this last sentence.
xingx1 2014/07/30 03:44:19 Done.
277 bool DataReductionProxyTamperDetection::IsContentLengthHeaderTampered(
278 const std::string& fingerprint) const {
279 int received_content_length_fingerprint, actual_content_length;
280 // If Content-Length value from data reduction proxy does not exist or it
bengr 2014/07/28 21:56:47 from the data reduction...
xingx1 2014/07/30 03:44:18 Done.
281 // cannot be converted to an integer, abort.
282 if (base::StringToInt(fingerprint, &received_content_length_fingerprint)) {
283 std::string actual_content_length_string;
284 // If there is no Content-Length header received, abort.
bengr 2014/07/28 21:56:48 You can move the "abort" first, here and elsewhere
xingx1 2014/07/30 03:44:18 Done. all other places.
285 if (response_headers_->GetNormalizedHeader("Content-Length",
286 &actual_content_length_string)) {
287 // If the Content-Length value cannot be converted to integer, abort.
288 if (!base::StringToInt(actual_content_length_string,
289 &actual_content_length)) {
290 return false;
291 }
292
293 return received_content_length_fingerprint != actual_content_length;
294 }
295 }
296 return false;
297 }
298
299 void DataReductionProxyTamperDetection::ReportContentLengthHeaderTamperedUMA()
300 const {
301 // Gets MIME type of the response and reports to UMA histograms separately.
302 // Divides MIME types into 4 groups: JavaScript, CSS, Images, and others.
303 REPORT_TAMPER_DETECTION_UMA(
304 is_secure_scheme_,
305 "DataReductionProxy.HTTPSHeaderTampered_ContentLength",
306 "DataReductionProxy.HTTPHeaderTampered_ContentLength",
307 carrier_id_);
308
309 // Gets MIME type.
310 std::string mime_type;
311 response_headers_->GetMimeType(&mime_type);
312
313 // Reports tampered JavaScript.
314 if (mime_type.compare("text/javascript") == 0 ||
315 mime_type.compare("application/x-javascript") == 0 ||
316 mime_type.compare("application/javascript") == 0) {
317 REPORT_TAMPER_DETECTION_UMA(
318 is_secure_scheme_,
319 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_JS",
320 "DataReductionProxy.HTTPHeaderTampered_ContentLength_JS",
321 carrier_id_);
322 }
323 // Reports tampered CSSs.
324 else if (mime_type.compare("text/css") == 0) {
325 REPORT_TAMPER_DETECTION_UMA(
326 is_secure_scheme_,
327 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_CSS",
328 "DataReductionProxy.HTTPHeaderTampered_ContentLength_CSS",
329 carrier_id_);
330 }
331 // Reports tampered images.
332 else if (mime_type.find("image/") == 0) {
333 REPORT_TAMPER_DETECTION_UMA(
334 is_secure_scheme_,
335 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Image",
336 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Image",
337 carrier_id_);
338 }
339 // Reports tampered other MIME types.
340 else {
341 REPORT_TAMPER_DETECTION_UMA(
342 is_secure_scheme_,
343 "DataReductionProxy.HTTPSHeaderTampered_ContentLength_Other",
344 "DataReductionProxy.HTTPHeaderTampered_ContentLength_Other",
345 carrier_id_);
346 }
347 }
348
349 DataReductionProxyTamperDetection::FingerprintCode
350 DataReductionProxyTamperDetection::GetFingerprintCode(
351 const std::string& fingerprint_name) {
352 std::map<std::string, FingerprintCode>::iterator it =
353 fingerprint_name_code_map_.find(fingerprint_name);
354
355 if (it != fingerprint_name_code_map_.end())
356 return it->second;
357 return NONEXIST;
358 }
359
360 // Removes the Chrome-Proxy header's fingerprint (action name
bengr 2014/07/28 21:56:48 What if there is no such fingerprint? If you expec
xingx1 2014/07/30 03:44:18 Done.
361 // |kFingerprintChromeProxy|) from its values vector.
362 void DataReductionProxyTamperDetection::RemoveChromeProxyFingerprint(
363 std::vector<std::string>* values) {
364 DCHECK(values);
365 if (!values) return;
366
367 std::string chrome_proxy_fingerprint_prefix = std::string(
368 kChromeProxyActionFingerprintChromeProxy) + "=";
369
370 for (size_t i = 0; i < values->size(); ++i) {
371 if ((*values)[i].find(chrome_proxy_fingerprint_prefix) == 0) {
372 values->erase(values->begin() + i);
373 break;
374 }
375 }
376 }
377
378 // We construct a canonical representation of the header so that reordered
379 // header values will produce the same fingerprint. The fingerprint is
380 // constructed as follows:
381 // 1) sorts the values;
bengr 2014/07/28 21:56:49 1) sort the values
xingx1 2014/07/30 03:44:17 Done.
382 // 2) concatenates sorted values with a "," delimiter.
bengr 2014/07/28 21:56:48 concatenate
xingx1 2014/07/30 03:44:17 Done.
383 std::string DataReductionProxyTamperDetection::ValuesToSortedString(
384 std::vector<std::string>* values) {
385 std::string concatenated_values;
bengr 2014/07/28 21:56:49 Why not make this a parameter. E.g., ValuesToSorte
xingx1 2014/07/30 03:44:18 Done.
386 DCHECK(values);
387 if (!values) return "";
388
389 std::sort(values->begin(), values->end());
390 for (size_t i = 0; i < values->size(); ++i) {
391 // Concatenates with delimiter ",".
392 concatenated_values += (*values)[i] + ",";
393 }
394 return concatenated_values;
395 }
396
397 std::string DataReductionProxyTamperDetection::GetMD5(
398 const std::string &input) {
399 base::MD5Digest digest;
400 base::MD5Sum(input.c_str(), input.size(), &digest);
401 return std::string((char*)digest.a, ARRAYSIZE_UNSAFE(digest.a));
bengr 2014/07/28 21:56:48 Likewise, the output could be a parameter of the m
xingx1 2014/07/30 03:44:17 To avoid return value copying, right? Done.
402 }
403
404 std::vector<std::string> DataReductionProxyTamperDetection::GetHeaderValues(
405 const net::HttpResponseHeaders* headers, const std::string& header_name) {
406 std::vector<std::string> values;
407 std::string value;
408 void* iter = NULL;
409 while (headers->EnumerateHeader(&iter, header_name, &value)) {
410 values.push_back(value);
411 }
412 return values;
413 }
414
415 } // namespace data_reduction_proxy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698