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 #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 | |
OLD | NEW |