| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2017 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 #import <Cronet/Cronet.h> |
| 6 |
| 7 #include "components/cronet/ios/test/start_cronet.h" |
| 8 #include "components/grpc_support/test/quic_test_server.h" |
| 9 #include "cronet_test_base.h" |
| 10 #include "net/base/mac/url_conversions.h" |
| 11 #include "net/cert/mock_cert_verifier.h" |
| 12 #include "net/test/cert_test_util.h" |
| 13 #include "net/test/test_data_directory.h" |
| 14 |
| 15 #include "testing/gtest_mac.h" |
| 16 |
| 17 namespace cronet { |
| 18 |
| 19 static const bool kINCLUDE_SUBDOMAINS = true; |
| 20 static const bool kEXCLUDE_SUBDOMAINS = false; |
| 21 static const std::string kSERVER_CERT = "quic_test.example.com.crt"; |
| 22 static NSDate* distant_future = [NSDate distantFuture]; |
| 23 |
| 24 // Tests public-key-pinning functionality. |
| 25 class PkpTest : public CronetTestBase { |
| 26 protected: |
| 27 void SetUp() override { |
| 28 CronetTestBase::SetUp(); |
| 29 |
| 30 NSString* full_url_str = [NSString |
| 31 stringWithFormat:@"%@:%i", |
| 32 [NSString |
| 33 stringWithCString:grpc_support::kTestServerUrl |
| 34 encoding:NSUTF8StringEncoding], |
| 35 grpc_support::GetQuicTestServerPort()]; |
| 36 request_url_ = [NSURL URLWithString:full_url_str]; |
| 37 server_host_ = request_url_.host; |
| 38 NSArray<NSString*>* components = [server_host_ |
| 39 componentsSeparatedByCharactersInSet: |
| 40 [NSCharacterSet characterSetWithCharactersInString:@"."]]; |
| 41 domain_ = |
| 42 [NSString stringWithFormat:@"%@.%@", components[components.count - 2], |
| 43 components[components.count - 1]]; |
| 44 |
| 45 // Create a Cronet enabled NSURLSession |
| 46 NSURLSessionConfiguration* sessionConfig = |
| 47 [NSURLSessionConfiguration defaultSessionConfiguration]; |
| 48 [Cronet installIntoSessionConfiguration:sessionConfig]; |
| 49 url_session_ = [NSURLSession sessionWithConfiguration:sessionConfig |
| 50 delegate:delegate_ |
| 51 delegateQueue:nil]; |
| 52 |
| 53 // Set mock cert verifier |
| 54 [Cronet setMockCertVerifier:CreateMockCertVerifier({kSERVER_CERT}, YES)]; |
| 55 } |
| 56 |
| 57 void TearDown() override { |
| 58 [Cronet shutdownForTesting]; |
| 59 CronetTestBase::TearDown(); |
| 60 } |
| 61 |
| 62 // Sends a request to a given URL, waits for the response and asserts that |
| 63 // the response doesn't contain an error. |
| 64 void sendRequestAndAssertSuccess(NSURL* url) { |
| 65 sendRequestAndAssertResult(url, true); |
| 66 } |
| 67 |
| 68 // Sends a request to a given URL, waits for the response and asserts that |
| 69 // the response contains an error. |
| 70 void sendRequestAndAssertError(NSURL* url) { |
| 71 sendRequestAndAssertResult(url, false); |
| 72 } |
| 73 |
| 74 // Sends a request to a given URL, waits for the response and asserts that |
| 75 // the response is either successful or containing an error depending on |
| 76 // the value of the passed |expected_success| parameter. |
| 77 void sendRequestAndAssertResult(NSURL* url, bool expected_success) { |
| 78 NSURLSessionDataTask* dataTask = |
| 79 [url_session_ dataTaskWithURL:request_url_]; |
| 80 StartDataTaskAndWaitForCompletion(dataTask); |
| 81 if (expected_success) { |
| 82 ASSERT_TRUE(IsResponseSuccessful()); |
| 83 } else { |
| 84 ASSERT_FALSE(IsResponseSuccessful()); |
| 85 } |
| 86 } |
| 87 |
| 88 // Adds a give public-key-pin and starts a Cronet engine for testing. |
| 89 static void AddPkpAndStartCronet(NSString* host, |
| 90 NSData* hash, |
| 91 BOOL include_subdomains, |
| 92 const NSDate* expiration_date) { |
| 93 NSSet* hashes = [NSSet setWithObject:hash]; |
| 94 [Cronet addPublicKeyPinsForHost:host |
| 95 pinHashes:hashes |
| 96 includeSubdomains:include_subdomains |
| 97 expirationDate:(NSDate*)expiration_date]; |
| 98 StartCronet(grpc_support::GetQuicTestServerPort()); |
| 99 } |
| 100 |
| 101 // Returns an arbitrary public key hash that doesn't match with any test |
| 102 // certificate. |
| 103 static NSData* NonMatchingHash() { |
| 104 const int length = 32; |
| 105 u_char hash[length]; |
| 106 memset(hash, 77, length); |
| 107 return [NSData dataWithBytes:hash length:length]; |
| 108 } |
| 109 |
| 110 NSURLSession* url_session_; |
| 111 NSURL* request_url_; // "https://test.example.com/hello.txt:62922" |
| 112 NSString* server_host_; // test.example.com |
| 113 NSString* domain_; // example.com |
| 114 |
| 115 }; // class PkpTest |
| 116 |
| 117 // Tests the case when a mismatching pin is set for some host that is |
| 118 // different from the one the client wants to access. In that case the other |
| 119 // host pinning policy should not be applied and the client is expected to |
| 120 // receive the successful response with the response code 200. |
| 121 TEST_F(PkpTest, TestSuccessIfPinSetForDifferentHost) { |
| 122 AddPkpAndStartCronet(@"some-other-host.com", NonMatchingHash(), |
| 123 kEXCLUDE_SUBDOMAINS, distant_future); |
| 124 ASSERT_NO_FATAL_FAILURE(sendRequestAndAssertSuccess(request_url_)); |
| 125 } |
| 126 |
| 127 // Tests the case when the pin hash does not match. The client is expected to |
| 128 // receive the error response. |
| 129 TEST_F(PkpTest, TestErrorIfPinDoesNotMatch) { |
| 130 AddPkpAndStartCronet(server_host_, NonMatchingHash(), kEXCLUDE_SUBDOMAINS, |
| 131 distant_future); |
| 132 ASSERT_NO_FATAL_FAILURE(sendRequestAndAssertError(request_url_)); |
| 133 } |
| 134 |
| 135 // Tests the case when the pin hash matches. The client is expected to |
| 136 // receive the successful response with the response code 200. |
| 137 TEST_F(PkpTest, TestSuccessIfPinMatches) { |
| 138 // Calculate hash of the server certificate |
| 139 scoped_refptr<net::X509Certificate> cert = |
| 140 net::ImportCertFromFile(net::GetTestCertsDirectory(), kSERVER_CERT); |
| 141 net::HashValue hash_value; |
| 142 CalculatePublicKeySha256(*cert, &hash_value); |
| 143 ASSERT_EQ(32ul, hash_value.size()); |
| 144 NSData* matching_hash = |
| 145 [NSData dataWithBytes:hash_value.data() length:hash_value.size()]; |
| 146 |
| 147 AddPkpAndStartCronet(server_host_, matching_hash, kEXCLUDE_SUBDOMAINS, |
| 148 distant_future); |
| 149 ASSERT_NO_FATAL_FAILURE(sendRequestAndAssertSuccess(request_url_)); |
| 150 } |
| 151 |
| 152 // Tests the case when the pin hash does not match and the client accesses the |
| 153 // subdomain of the configured PKP host with includeSubdomains flag set to true. |
| 154 // The client is expected to receive the error response. |
| 155 TEST_F(PkpTest, TestIncludeSubdomainsFlagEqualTrue) { |
| 156 AddPkpAndStartCronet(domain_, NonMatchingHash(), kINCLUDE_SUBDOMAINS, |
| 157 distant_future); |
| 158 ASSERT_NO_FATAL_FAILURE(sendRequestAndAssertError(request_url_)); |
| 159 } |
| 160 |
| 161 // Tests the case when the pin hash does not match and the client accesses the |
| 162 // subdomain of the configured PKP host with includeSubdomains flag set to |
| 163 // false. The client is expected to receive the successful response with the |
| 164 // response code 200. |
| 165 TEST_F(PkpTest, TestIncludeSubdomainsFlagEqualFalse) { |
| 166 AddPkpAndStartCronet(domain_, NonMatchingHash(), kEXCLUDE_SUBDOMAINS, |
| 167 distant_future); |
| 168 ASSERT_NO_FATAL_FAILURE(sendRequestAndAssertSuccess(request_url_)); |
| 169 } |
| 170 |
| 171 // Tests a mismatching pin that will expire in 10 seconds. The pins should be |
| 172 // still valid and enforced during the request; thus returning the pin match |
| 173 // error. |
| 174 TEST_F(PkpTest, TestSoonExpiringPin) { |
| 175 AddPkpAndStartCronet(server_host_, NonMatchingHash(), kEXCLUDE_SUBDOMAINS, |
| 176 [NSDate dateWithTimeIntervalSinceNow:10]); |
| 177 ASSERT_NO_FATAL_FAILURE(sendRequestAndAssertError(request_url_)); |
| 178 } |
| 179 |
| 180 // Tests mismatching pin that expired 1 second ago. Since the pin has |
| 181 // expired, it should not be enforced during the request; thus a successful |
| 182 // response is expected. |
| 183 TEST_F(PkpTest, TestRecentlyExpiredPin) { |
| 184 AddPkpAndStartCronet(server_host_, NonMatchingHash(), kEXCLUDE_SUBDOMAINS, |
| 185 [NSDate dateWithTimeIntervalSinceNow:-1]); |
| 186 ASSERT_NO_FATAL_FAILURE(sendRequestAndAssertSuccess(request_url_)); |
| 187 } |
| 188 |
| 189 // Tests that host pinning is not persisted between multiple CronetEngine |
| 190 // instances. |
| 191 TEST_F(PkpTest, TestPinsAreNotPersisted) { |
| 192 // |
| 193 AddPkpAndStartCronet(server_host_, NonMatchingHash(), kEXCLUDE_SUBDOMAINS, |
| 194 distant_future); |
| 195 ASSERT_NO_FATAL_FAILURE(sendRequestAndAssertError(request_url_)); |
| 196 [Cronet shutdownForTesting]; |
| 197 |
| 198 // Restart Cronet engine and try the same request again. Since the pins are |
| 199 // not persisted, a successful response is expected. |
| 200 StartCronet(grpc_support::GetQuicTestServerPort()); |
| 201 ASSERT_NO_FATAL_FAILURE(sendRequestAndAssertSuccess(request_url_)); |
| 202 } |
| 203 |
| 204 } // namespace cronet |
| OLD | NEW |