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.
5 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.h"
12 #include "base/base64.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_split.h"
17 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
18 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers_test_utils.h"
19 #include "net/http/http_response_headers.h"
20 #include "testing/gtest/include/gtest/gtest.h"
22 #if defined(OS_ANDROID)
23 #include "base/android/jni_android.h"
24 #include "net/android/network_library.h"
29 // Calcuates MD5 hash value for a string and then base64 encode it. Testcases
30 // contain expected fingerprint in plain text, which needs to be encoded before
32 std::string GetEncoded(const std::string& input) {
33 base::MD5Digest digest;
34 base::MD5Sum(input.c_str(), input.size(), &digest);
35 std::string base64encoded;
36 base::Base64Encode(std::string((char*)digest.a,
37 ARRAYSIZE_UNSAFE(digest.a)), &base64encoded);
41 // Replaces all contents within "[]" by corresponding base64 encoded MD5 value.
42 // It can handle nested case like: [[abc]def]. This helper function transforms
43 // fingerprint in plain text to actual encoded fingerprint.
44 void ReplaceWithEncodedString(std::string* input) {
45 size_t start, end, temp;
47 start = input->find("[");
48 if (start == std::string::npos) break;
50 temp = input->find("[", start + 1);
51 end = input->find("]", start + 1);
52 if (end != std::string::npos && end < temp)
57 std::string need_to_encode = input->substr(start + 1, end - start - 1);
58 *input = input->substr(0, start) + GetEncoded(need_to_encode) +
59 input->substr(end + 1);
63 // Returns a vector contains all the values from a comma-separated string.
64 // Some testcases contain string representation of a vector, this helper
65 // function generates a vector from a input string.
66 std::vector<std::string> StringsToVector(const std::string& values) {
67 std::vector<std::string> ret;
72 while ((next = values.find(",", now)) != std::string::npos) {
73 ret.push_back(values.substr(now, next - now));
80 #if defined(OS_ANDROID)
81 JNIEnv* env = base::android::AttachCurrentThread();
82 static bool inited = false;
84 net::android::RegisterNetworkLibrary(env);
92 namespace data_reduction_proxy {
94 class DataReductionProxyTamperDetectionTest : public testing::Test {
98 // Tests function ValidateChromeProxyHeader.
99 TEST_F(DataReductionProxyTamperDetectionTest, ChromeProxy) {
100 // |received_fingerprint| is not the actual fingerprint from data reduction
101 // proxy, instead, the base64 encoded field is in plain text (within "[]")
102 // and needs to be encoded first.
105 std::string raw_header;
106 std::string received_fingerprint;
107 bool expected_tampered_with;
112 "Chrome-Proxy: c,b,a,3,2,1,fcp=f\n",
117 "Checks Chrome-Proxy's fingerprint removing.",
119 "Chrome-Proxy: a,b,c,d,e,3,2,1,fcp=f\n",
120 "[1,2,3,a,b,c,d,e,]",
124 "Checks no Chrome-Proxy header case (should not happen).",
130 "Checks empty Chrome-Proxy header case (should not happen).",
137 "Checks Chrome-Proxy header with its fingerprint only case.",
139 "Chrome-Proxy: fcp=f\n",
144 "Checks empty Chrome-Proxy header case, with extra ',' and ' '",
146 "Chrome-Proxy: fcp=f , \n",
151 "Changed no value to empty value.",
153 "Chrome-Proxy: fcp=f\n",
158 "Changed header values.",
160 "Chrome-Proxy: a,b=2,c,d=1,fcp=f\n",
165 "Changed order of header values.",
167 "Chrome-Proxy: c,b,a,fcp=1\n",
172 "Checks Chrome-Proxy header with extra ' '.",
174 "Chrome-Proxy: a , b , c, d, fcp=f\n",
179 "Check Chrome-Proxy header with multiple lines and ' '.",
181 "Chrome-Proxy: a , c , d, fcp=f \n"
182 "Chrome-Proxy: b \n",
187 "Checks Chrome-Proxy header with multiple lines, at different positions",
197 "Chrome-Proxy: fcp=f \n"
199 "Content-Length: 12345\n",
204 "Checks Chrome-Proxy header with multiple same values.",
209 "Chrome-Proxy: d, fcp=f \n"
210 "Chrome-Proxy: a \n",
215 "Changed Chrome-Proxy header with multiple lines..",
220 "Chrome-Proxy: c,fcp=f\n",
225 "Checks case whose received fingerprint is empty.",
227 "Chrome-Proxy: a,b,c,fcp=1\n",
232 "Checks case whose received fingerprint cannot be base64 decoded.",
234 "Chrome-Proxy: a,b,c,fcp=1\n",
235 "not_base64_encoded",
240 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
241 ReplaceWithEncodedString(&test[i].received_fingerprint);
243 std::string raw_headers(test[i].raw_header);
244 HeadersToRaw(&raw_headers);
245 scoped_refptr<net::HttpResponseHeaders> headers(
246 new net::HttpResponseHeaders(raw_headers));
248 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0);
250 bool tampered = tamper_detection.ValidateChromeProxyHeader(
251 test[i].received_fingerprint);
253 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label;
257 // Tests function ValidateViaHeader.
258 TEST_F(DataReductionProxyTamperDetectionTest, Via) {
261 std::string raw_header;
262 std::string received_fingerprint;
263 bool expected_tampered_with;
264 bool expected_has_chrome_proxy_via_header;
267 "Checks the case that Chrome-Compression-Proxy occurs at the last.",
269 "Via: a, b, c, 1.1 Chrome-Compression-Proxy\n",
275 "Checks when there is intermediary.",
277 "Via: a, b, c, 1.1 Chrome-Compression-Proxy, xyz\n",
283 "Checks the case of empty Via header.",
291 "Checks the case that only the data reduction proxy's Via header occurs.",
293 "Via: 1.1 Chrome-Compression-Proxy \n",
299 "Checks the case that there are ' ', i.e., empty value after the data"
300 " reduction proxy's Via header.",
302 "Via: 1.1 Chrome-Compression-Proxy , , \n",
308 "Checks the case when there is no Via header",
314 // Same to above test cases, but with deprecated data reduciton proxy Via
317 "Checks the case that Chrome Compression Proxy occurs at the last.",
319 "Via: a, b, c, 1.1 Chrome Compression Proxy\n",
325 "Checks when there is intermediary.",
327 "Via: a, b, c, 1.1 Chrome Compression Proxy, xyz\n",
333 "Checks the case of empty Via header.",
341 "Checks the case that only the data reduction proxy's Via header occurs.",
343 "Via: 1.1 Chrome Compression Proxy \n",
349 "Checks the case that there are ' ', i.e., empty value after the data"
350 "reduction proxy's Via header.",
352 "Via: 1.1 Chrome Compression Proxy , , \n",
358 "Checks the case when there is no Via header",
366 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
367 std::string raw_headers(test[i].raw_header);
368 HeadersToRaw(&raw_headers);
369 scoped_refptr<net::HttpResponseHeaders> headers(
370 new net::HttpResponseHeaders(raw_headers));
372 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0);
374 bool has_chrome_proxy_via_header;
375 bool tampered = tamper_detection.ValidateViaHeader(
376 test[i].received_fingerprint, &has_chrome_proxy_via_header);
378 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label;
379 EXPECT_EQ(test[i].expected_has_chrome_proxy_via_header,
380 has_chrome_proxy_via_header) << test[i].label;
384 // Tests function ValidateOtherHeaders.
385 TEST_F(DataReductionProxyTamperDetectionTest, OtherHeaders) {
386 // For following testcases, |received_fingerprint| is not the actual
387 // fingerprint from data reduction proxy, instead, the base64 encoded field
388 // is in plain text (within "[]") and needs to be encoded first. For example,
389 // "[12345;]|content-length" needs to be encoded to
390 // "Base64Encoded(MD5(12345;))|content-length" before calling the checking
394 std::string raw_header;
395 std::string received_fingerprint;
396 bool expected_tampered_with;
399 "Checks the case that only one header is requested.",
401 "Content-Length: 12345\n",
402 "[12345,;]|content-length",
406 "Checks the case that there is only one requested header and it does not"
409 "[;]|non_exist_header",
413 "Checks the case of multiple headers are requested.",
420 "[1,;2,;3,;4,;5,;]|content-type|cache-control|etag|connection|expires",
424 "Checks the case that one header has multiple values.",
426 "Content-Type: aaa1, bbb1, ccc1\n"
427 "Cache-Control: aaa2\n",
428 "[aaa1,bbb1,ccc1,;aaa2,;]|content-type|cache-control",
432 "Checks the case that one header has multiple lines.",
434 "Content-Type: aaa1, ccc1\n"
435 "Content-Type: xxx1, bbb1, ccc1\n"
436 "Cache-Control: aaa2\n",
437 "[aaa1,bbb1,ccc1,ccc1,xxx1,;aaa2,;]|content-type|cache-control",
441 "Checks the case that more than one headers have multiple values.",
443 "Content-Type: aaa1, ccc1\n"
444 "Cache-Control: ccc2 , bbb2\n"
445 "Content-Type: bbb1, ccc1\n"
446 "Cache-Control: aaa2 \n",
447 "[aaa1,bbb1,ccc1,ccc1,;aaa2,bbb2,ccc2,;]|content-type|cache-control",
451 "Checks the case that one of the requested headers is missing (Expires).",
453 "Content-Type: aaa1, ccc1\n",
454 "[aaa1,ccc1,;;]|content-type|expires",
458 "Checks the case that some of the requested headers have empty value.",
462 "[,;,;]|content-type|cache-control",
466 "Checks the case that all the requested headers are missing.",
468 "[;;]|content-type|expires",
472 "Checks the case that some headers are missing, some of them are empty.",
475 "[;,;]|content-type|cache-control",
479 "Checks the case there is no requested header (header list is empty).",
481 "Chrome-Proxy: aut=aauutthh,bbbypas=0,aaxxx=xxx,bbbloc=1\n"
483 "Cache-Control: 2\n",
488 "Checks tampered requested header values.",
490 "Content-Type: aaa1, ccc1\n"
491 "Cache-Control: ccc2 , bbb2\n",
492 "[aaa1,bbb1,;bbb2,ccc2,;]|content-type|cache-control",
497 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
498 ReplaceWithEncodedString(&test[i].received_fingerprint);
500 std::string raw_headers(test[i].raw_header);
501 HeadersToRaw(&raw_headers);
502 scoped_refptr<net::HttpResponseHeaders> headers(
503 new net::HttpResponseHeaders(raw_headers));
505 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0);
507 bool tampered = tamper_detection.ValidateOtherHeaders(
508 test[i].received_fingerprint);
510 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label;
514 // Tests function ValidateContentLengthHeader.
515 TEST_F(DataReductionProxyTamperDetectionTest, ContentLength) {
518 std::string raw_header;
519 std::string received_fingerprint;
520 bool expected_tampered_with;
523 "Checks the case fingerprint matches received response.",
525 "Content-Length: 12345\n",
530 "Checks case that response got modified.",
532 "Content-Length: 12345\n",
537 "Checks the case that the data reduction proxy has not sent"
538 "Content-Length header.",
540 "Content-Length: 12345\n",
545 "Checks the case that the data reduction proxy sends invalid"
546 "Content-Length header.",
548 "Content-Length: 12345\n",
553 "Checks the case that the data reduction proxy sends invalid"
554 "Content-Length header.",
556 "Content-Length: aaa\n",
561 "Checks the case that Content-Length header is missing at the Chromium"
568 "Checks the case that Content-Length header are missing at both end.",
575 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
576 std::string raw_headers(test[i].raw_header);
577 HeadersToRaw(&raw_headers);
578 scoped_refptr<net::HttpResponseHeaders> headers(
579 new net::HttpResponseHeaders(raw_headers));
581 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0);
583 bool tampered = tamper_detection.ValidateContentLengthHeader(
584 test[i].received_fingerprint);
586 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label;
590 // Tests ValuesToSortedString function.
591 TEST_F(DataReductionProxyTamperDetectionTest, ValuesToSortedString) {
594 std::string input_values;
595 std::string expected_output_string;
598 "Checks the correctness of sorting.",
603 "Checks the case that there is an empty input vector.",
608 "Checks the case that there is an empty string in the input vector.",
614 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
615 std::vector<std::string> input_values =
616 StringsToVector(test[i].input_values);
617 std::string output_string =
618 DataReductionProxyTamperDetection::ValuesToSortedString(&input_values);
619 EXPECT_EQ(output_string, test[i].expected_output_string) << test[i].label;
623 // Tests GetHeaderValues function.
624 TEST_F(DataReductionProxyTamperDetectionTest, GetHeaderValues) {
627 std::string raw_header;
628 std::string header_name;
629 std::string expected_output_values;
632 "Checks the correctness of getting single line header.",
639 "Checks the correctness of getting multiple lines header.",
645 "1,2,3,4,5,6,7,8,9,",
648 "Checks the correctness of getting missing header.",
654 "Checks the correctness of getting empty header.",
662 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
663 std::string raw_headers(test[i].raw_header);
664 HeadersToRaw(&raw_headers);
665 scoped_refptr<net::HttpResponseHeaders> headers(
666 new net::HttpResponseHeaders(raw_headers));
668 std::vector<std::string> expected_output_values =
669 StringsToVector(test[i].expected_output_values);
671 std::vector<std::string> output_values =
672 DataReductionProxyTamperDetection::GetHeaderValues(headers.get(),
673 test[i].header_name);
674 EXPECT_EQ(expected_output_values, output_values) << test[i].label;
678 // Tests main function DetectAndReport.
679 TEST_F(DataReductionProxyTamperDetectionTest, DetectAndReport) {
682 std::string raw_header;
683 bool expected_tampered_with;
686 "Check no fingerprint added case.",
688 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n"
689 "Content-Length: 12345\n"
690 "Chrome-Proxy: bypass=0\n",
694 "Check the case Chrome-Proxy fingerprint doesn't match.",
696 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n"
697 "Content-Length: 12345\n"
698 "header1: header_1\n"
699 "header2: header_2\n"
700 "header3: header_3\n"
701 "Chrome-Proxy: fcl=12345, "
702 "foh=[header_1,;header_2,;header_3,;]|header1|header2|header3,fvia=0,"
707 "Check the case response matches the fingerprint completely.",
709 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n"
710 "Content-Length: 12345\n"
711 "header1: header_1\n"
712 "header2: header_2\n"
713 "header3: header_3\n"
714 "Chrome-Proxy: fcl=12345, "
715 "foh=[header_1,;header_2,;header_3,;]|header1|header2|header3,"
716 "fvia=0, fcp=[fcl=12345,foh=[header_1,;header_2,;header_3,;]"
717 "|header1|header2|header3,fvia=0,]\n",
721 "Check the case that Content-Length doesn't match.",
723 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n"
724 "Content-Length: 0\n"
725 "header1: header_1\n"
726 "header2: header_2\n"
727 "header3: header_3\n"
728 "Chrome-Proxy: fcl=12345, "
729 "foh=[header_1,;header_2,;header_3,;]|header1|header2|header3, fvia=0, "
730 "fcp=[fcl=12345,foh=[header_1,;header_2,;header_3,;]|"
731 "header1|header2|header3,fvia=0,]\n",
738 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
739 std::string raw_headers(test[i].raw_header);
740 ReplaceWithEncodedString(&raw_headers);
741 HeadersToRaw(&raw_headers);
742 scoped_refptr<net::HttpResponseHeaders> headers(
743 new net::HttpResponseHeaders(raw_headers));
746 test[i].expected_tampered_with,
747 DataReductionProxyTamperDetection::DetectAndReport(headers.get(), true))