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