1 // Copyright 2015 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net; 6 7 import static com.google.common.truth.Truth.assertThat; 8 import static com.google.common.truth.Truth.assertWithMessage; 9 10 import static org.junit.Assert.assertThrows; 11 import static org.junit.Assert.fail; 12 13 import static org.chromium.net.Http2TestServer.SERVER_CERT_PEM; 14 import static org.chromium.net.truth.UrlResponseInfoSubject.assertThat; 15 16 import android.os.Build; 17 18 import androidx.test.ext.junit.runners.AndroidJUnit4; 19 import androidx.test.filters.SmallTest; 20 21 import org.json.JSONObject; 22 import org.junit.After; 23 import org.junit.Before; 24 import org.junit.Rule; 25 import org.junit.Test; 26 import org.junit.runner.RunWith; 27 28 import org.chromium.base.test.util.DoNotBatch; 29 import org.chromium.net.CronetTestRule.CronetImplementation; 30 import org.chromium.net.CronetTestRule.CronetTestFramework; 31 import org.chromium.net.CronetTestRule.IgnoreFor; 32 import org.chromium.net.test.util.CertTestUtil; 33 34 import java.io.ByteArrayInputStream; 35 import java.security.cert.CertificateFactory; 36 import java.security.cert.X509Certificate; 37 import java.util.Arrays; 38 import java.util.Calendar; 39 import java.util.Date; 40 import java.util.HashSet; 41 import java.util.Set; 42 43 /** Public-Key-Pinning tests of Cronet Java API. */ 44 @DoNotBatch(reason = "crbug/1459563") 45 @RunWith(AndroidJUnit4.class) 46 @IgnoreFor( 47 implementations = {CronetImplementation.FALLBACK}, 48 reason = "The fallback implementation does not support public key pinning") 49 public class PkpTest { 50 private static final int DISTANT_FUTURE = Integer.MAX_VALUE; 51 private static final boolean INCLUDE_SUBDOMAINS = true; 52 private static final boolean EXCLUDE_SUBDOMAINS = false; 53 private static final boolean ENABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS = true; 54 private static final boolean DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS = false; 55 56 @Rule public final CronetTestRule mTestRule = CronetTestRule.withManualEngineStartup(); 57 58 @Before setUp()59 public void setUp() throws Exception { 60 assertThat(Http2TestServer.startHttp2TestServer(mTestRule.getTestFramework().getContext())) 61 .isTrue(); 62 } 63 64 @After tearDown()65 public void tearDown() throws Exception { 66 assertThat(Http2TestServer.shutdownHttp2TestServer()).isTrue(); 67 } 68 69 /** 70 * Tests the case when the pin hash does not match. The client is expected to receive the error 71 * response. 72 */ 73 @Test 74 @SmallTest testErrorCodeIfPinDoesNotMatch()75 public void testErrorCodeIfPinDoesNotMatch() throws Exception { 76 applyCronetEngineBuilderConfigurationPatch( 77 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 78 byte[] nonMatchingHash = generateSomeSha256(); 79 applyPkpSha256Patch( 80 mTestRule.getTestFramework(), 81 Http2TestServer.getServerHost(), 82 nonMatchingHash, 83 EXCLUDE_SUBDOMAINS, 84 DISTANT_FUTURE); 85 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 86 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 87 UrlRequest.Builder requestBuilder = 88 engine.newUrlRequestBuilder( 89 Http2TestServer.getServerUrl(), callback, callback.getExecutor()); 90 requestBuilder.build().start(); 91 callback.blockForDone(); 92 93 assertErrorResponse(callback); 94 } 95 96 /** 97 * Tests the case when the pin hash matches. The client is expected to receive the successful 98 * response with the response code 200. 99 */ 100 @Test 101 @SmallTest testSuccessIfPinMatches()102 public void testSuccessIfPinMatches() throws Exception { 103 applyCronetEngineBuilderConfigurationPatch( 104 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 105 // Get PKP hash of the real certificate 106 X509Certificate cert = readCertFromFileInPemFormat(SERVER_CERT_PEM); 107 byte[] matchingHash = CertTestUtil.getPublicKeySha256(cert); 108 109 applyPkpSha256Patch( 110 mTestRule.getTestFramework(), 111 Http2TestServer.getServerHost(), 112 matchingHash, 113 EXCLUDE_SUBDOMAINS, 114 DISTANT_FUTURE); 115 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 116 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 117 UrlRequest.Builder requestBuilder = 118 engine.newUrlRequestBuilder( 119 Http2TestServer.getServerUrl(), callback, callback.getExecutor()); 120 requestBuilder.build().start(); 121 callback.blockForDone(); 122 123 assertSuccessfulResponse(callback); 124 } 125 126 /** 127 * Tests the case when the pin hash does not match and the client accesses the subdomain of the 128 * configured PKP host with includeSubdomains flag set to true. The client is expected to 129 * receive the error response. 130 */ 131 @Test 132 @SmallTest 133 @IgnoreFor( 134 implementations = {CronetImplementation.AOSP_PLATFORM}, 135 reason = 136 "Requires the use of subdomains. This can currently only be done through" 137 + " HostResolverRules, which fakes hostname resultion." 138 + " TODO(crbug/1501033): Enable for HttpEngine once we have" 139 + " hostname resolution") testIncludeSubdomainsFlagEqualTrue()140 public void testIncludeSubdomainsFlagEqualTrue() throws Exception { 141 String fakeUrl = "https://test.example.com:8443"; 142 String fakeDomain = "example.com"; 143 144 applyCronetEngineBuilderConfigurationPatchWithMockCertVerifier( 145 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 146 byte[] nonMatchingHash = generateSomeSha256(); 147 applyPkpSha256Patch( 148 mTestRule.getTestFramework(), 149 fakeDomain, 150 nonMatchingHash, 151 INCLUDE_SUBDOMAINS, 152 DISTANT_FUTURE); 153 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 154 155 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 156 157 UrlRequest.Builder requestBuilder = 158 engine.newUrlRequestBuilder(fakeUrl, callback, callback.getExecutor()); 159 requestBuilder.build().start(); 160 callback.blockForDone(); 161 162 assertErrorResponse(callback); 163 } 164 165 /** 166 * Tests the case when the pin hash does not match and the client accesses the subdomain of the 167 * configured PKP host with includeSubdomains flag set to false. The client is expected to 168 * receive the successful response with the response code 200. 169 */ 170 @Test 171 @SmallTest 172 @IgnoreFor( 173 implementations = {CronetImplementation.AOSP_PLATFORM}, 174 reason = 175 "Requires the use of subdomains. This can currently only be done through" 176 + " HostResolverRules, which fakes hostname resultion." 177 + " TODO(crbug/1501033): Enable for HttpEngine once we have" 178 + " hostname resolution") testIncludeSubdomainsFlagEqualFalse()179 public void testIncludeSubdomainsFlagEqualFalse() throws Exception { 180 String fakeUrl = "https://test.example.com:8443"; 181 String fakeDomain = "example.com"; 182 183 applyCronetEngineBuilderConfigurationPatchWithMockCertVerifier( 184 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 185 byte[] nonMatchingHash = generateSomeSha256(); 186 applyPkpSha256Patch( 187 mTestRule.getTestFramework(), 188 fakeDomain, 189 nonMatchingHash, 190 EXCLUDE_SUBDOMAINS, 191 DISTANT_FUTURE); 192 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 193 194 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 195 196 UrlRequest.Builder requestBuilder = 197 engine.newUrlRequestBuilder(fakeUrl, callback, callback.getExecutor()); 198 requestBuilder.build().start(); 199 callback.blockForDone(); 200 201 assertSuccessfulResponse(callback); 202 } 203 204 /** 205 * Tests the case when the mismatching pin is set for some host that is different from the one 206 * the client wants to access. In that case the other host pinning policy should not be applied 207 * and the client is expected to receive the successful response with the response code 200. 208 */ 209 @Test 210 @SmallTest testSuccessIfNoPinSpecified()211 public void testSuccessIfNoPinSpecified() throws Exception { 212 applyCronetEngineBuilderConfigurationPatch( 213 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 214 byte[] nonMatchingHash = generateSomeSha256(); 215 applyPkpSha256Patch( 216 mTestRule.getTestFramework(), 217 "otherhost.com", 218 nonMatchingHash, 219 INCLUDE_SUBDOMAINS, 220 DISTANT_FUTURE); 221 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 222 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 223 UrlRequest.Builder requestBuilder = 224 engine.newUrlRequestBuilder( 225 Http2TestServer.getServerUrl(), callback, callback.getExecutor()); 226 requestBuilder.build().start(); 227 callback.blockForDone(); 228 229 assertSuccessfulResponse(callback); 230 } 231 232 /** 233 * Tests mismatching pins that will expire in 10 seconds. The pins should be still valid and 234 * enforced during the request; thus returning PIN mismatch error. 235 */ 236 @Test 237 @SmallTest testSoonExpiringPin()238 public void testSoonExpiringPin() throws Exception { 239 applyCronetEngineBuilderConfigurationPatch( 240 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 241 final int tenSecondsAhead = 10; 242 byte[] nonMatchingHash = generateSomeSha256(); 243 applyPkpSha256Patch( 244 mTestRule.getTestFramework(), 245 Http2TestServer.getServerHost(), 246 nonMatchingHash, 247 EXCLUDE_SUBDOMAINS, 248 tenSecondsAhead); 249 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 250 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 251 UrlRequest.Builder requestBuilder = 252 engine.newUrlRequestBuilder( 253 Http2TestServer.getServerUrl(), callback, callback.getExecutor()); 254 requestBuilder.build().start(); 255 callback.blockForDone(); 256 257 assertErrorResponse(callback); 258 } 259 260 /** 261 * Tests mismatching pins that expired 1 second ago. Since the pins have expired, they should 262 * not be enforced during the request; thus a successful response is expected. 263 */ 264 @Test 265 @SmallTest testRecentlyExpiredPin()266 public void testRecentlyExpiredPin() throws Exception { 267 applyCronetEngineBuilderConfigurationPatch( 268 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 269 final int oneSecondAgo = -1; 270 byte[] nonMatchingHash = generateSomeSha256(); 271 applyPkpSha256Patch( 272 mTestRule.getTestFramework(), 273 Http2TestServer.getServerHost(), 274 nonMatchingHash, 275 EXCLUDE_SUBDOMAINS, 276 oneSecondAgo); 277 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 278 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 279 UrlRequest.Builder requestBuilder = 280 engine.newUrlRequestBuilder( 281 Http2TestServer.getServerUrl(), callback, callback.getExecutor()); 282 requestBuilder.build().start(); 283 callback.blockForDone(); 284 285 assertSuccessfulResponse(callback); 286 } 287 288 /** 289 * Tests that the pinning of local trust anchors is enforced when pinning bypass for local trust 290 * anchors is disabled. 291 */ 292 @Test 293 @SmallTest testLocalTrustAnchorPinningEnforced()294 public void testLocalTrustAnchorPinningEnforced() throws Exception { 295 applyCronetEngineBuilderConfigurationPatch( 296 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 297 byte[] nonMatchingHash = generateSomeSha256(); 298 applyPkpSha256Patch( 299 mTestRule.getTestFramework(), 300 Http2TestServer.getServerHost(), 301 nonMatchingHash, 302 EXCLUDE_SUBDOMAINS, 303 DISTANT_FUTURE); 304 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 305 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 306 UrlRequest.Builder requestBuilder = 307 engine.newUrlRequestBuilder( 308 Http2TestServer.getServerUrl(), callback, callback.getExecutor()); 309 requestBuilder.build().start(); 310 callback.blockForDone(); 311 312 assertErrorResponse(callback); 313 } 314 315 /** 316 * Tests that the pinning of local trust anchors is not enforced when pinning bypass for local 317 * trust anchors is enabled. 318 */ 319 @Test 320 @SmallTest testLocalTrustAnchorPinningNotEnforced()321 public void testLocalTrustAnchorPinningNotEnforced() throws Exception { 322 applyCronetEngineBuilderConfigurationPatch( 323 mTestRule.getTestFramework(), ENABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 324 byte[] nonMatchingHash = generateSomeSha256(); 325 applyPkpSha256Patch( 326 mTestRule.getTestFramework(), 327 Http2TestServer.getServerHost(), 328 nonMatchingHash, 329 EXCLUDE_SUBDOMAINS, 330 DISTANT_FUTURE); 331 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 332 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 333 UrlRequest.Builder requestBuilder = 334 engine.newUrlRequestBuilder( 335 Http2TestServer.getServerUrl(), callback, callback.getExecutor()); 336 requestBuilder.build().start(); 337 callback.blockForDone(); 338 339 assertSuccessfulResponse(callback); 340 } 341 342 /** Tests that host pinning is not persisted between multiple CronetEngine instances. */ 343 @Test 344 @SmallTest testPinsAreNotPersisted()345 public void testPinsAreNotPersisted() throws Exception { 346 applyCronetEngineBuilderConfigurationPatch( 347 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 348 byte[] nonMatchingHash = generateSomeSha256(); 349 applyPkpSha256Patch( 350 mTestRule.getTestFramework(), 351 Http2TestServer.getServerHost(), 352 nonMatchingHash, 353 EXCLUDE_SUBDOMAINS, 354 DISTANT_FUTURE); 355 ExperimentalCronetEngine engine = mTestRule.getTestFramework().startEngine(); 356 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 357 UrlRequest.Builder requestBuilder = 358 engine.newUrlRequestBuilder( 359 Http2TestServer.getServerUrl(), callback, callback.getExecutor()); 360 requestBuilder.build().start(); 361 callback.blockForDone(); 362 assertErrorResponse(callback); 363 364 // Restart Cronet engine and try the same request again. Since the pins are not persisted, 365 // a successful response is expected. 366 engine.shutdown(); 367 ExperimentalCronetEngine.Builder builder = 368 mTestRule 369 .getTestFramework() 370 .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()); 371 372 configureCronetEngineBuilder(builder, DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 373 engine = builder.build(); 374 callback = new TestUrlRequestCallback(); 375 requestBuilder = 376 engine.newUrlRequestBuilder( 377 Http2TestServer.getServerUrl(), callback, callback.getExecutor()); 378 requestBuilder.build().start(); 379 callback.blockForDone(); 380 assertSuccessfulResponse(callback); 381 } 382 383 /** 384 * Tests that the client receives {@code InvalidArgumentException} when the pinned host name is 385 * invalid. 386 */ 387 @Test 388 @SmallTest testHostNameArgumentValidation()389 public void testHostNameArgumentValidation() throws Exception { 390 applyCronetEngineBuilderConfigurationPatch( 391 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 392 final String label63 = "123456789-123456789-123456789-123456789-123456789-123456789-123"; 393 final String host255 = label63 + "." + label63 + "." + label63 + "." + label63; 394 // Valid host names. 395 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), "domain.com"); 396 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), "my-domain.com"); 397 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), "section4.domain.info"); 398 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), "44.domain44.info"); 399 assertNoExceptionWhenHostNameIsValid( 400 mTestRule.getTestFramework(), "very.long.long.long.long.long.long.long.domain.com"); 401 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), "host"); 402 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), "новости.ру"); 403 assertNoExceptionWhenHostNameIsValid( 404 mTestRule.getTestFramework(), "самые-последние.новости.рус"); 405 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), "最新消息.中国"); 406 // Checks max size of the host label (63 characters) 407 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), label63 + ".com"); 408 // Checks max size of the host name (255 characters) 409 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), host255); 410 assertNoExceptionWhenHostNameIsValid(mTestRule.getTestFramework(), "127.0.0.z"); 411 412 // Invalid host names. 413 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "domain.com:300"); 414 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "-domain.com"); 415 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "domain-.com"); 416 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "http://domain.com"); 417 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "domain.com:"); 418 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "domain.com/"); 419 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "новости.ру:"); 420 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "новости.ру/"); 421 assertExceptionWhenHostNameIsInvalid( 422 mTestRule.getTestFramework(), "_http.sctp.www.example.com"); 423 assertExceptionWhenHostNameIsInvalid( 424 mTestRule.getTestFramework(), "http.sctp._www.example.com"); 425 // Checks a host that exceeds max allowed length of the host label (63 characters) 426 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), label63 + "4.com"); 427 // Checks a host that exceeds max allowed length of hostname (255 characters) 428 assertExceptionWhenHostNameIsInvalid( 429 mTestRule.getTestFramework(), host255.substring(3) + ".com"); 430 assertExceptionWhenHostNameIsInvalid( 431 mTestRule.getTestFramework(), "FE80:0000:0000:0000:0202:B3FF:FE1E:8329"); 432 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "[2001:db8:0:1]:80"); 433 434 // Invalid host names for PKP that contain IPv4 addresses 435 // or names with digits and dots only. 436 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "127.0.0.1"); 437 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "68.44.222.12"); 438 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "256.0.0.1"); 439 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "127.0.0.1.1"); 440 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "127.0.0"); 441 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "127.0.0."); 442 assertExceptionWhenHostNameIsInvalid(mTestRule.getTestFramework(), "127.0.0.299"); 443 } 444 445 /** 446 * Tests that NullPointerException is thrown if the host name or the collection of pins or the 447 * expiration date is null. 448 */ 449 @Test 450 @SmallTest testNullArguments()451 public void testNullArguments() throws Exception { 452 applyCronetEngineBuilderConfigurationPatch( 453 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 454 verifyExceptionWhenAddPkpArgumentIsNull(mTestRule.getTestFramework(), true, false, false); 455 verifyExceptionWhenAddPkpArgumentIsNull(mTestRule.getTestFramework(), false, true, false); 456 verifyExceptionWhenAddPkpArgumentIsNull(mTestRule.getTestFramework(), false, false, true); 457 verifyExceptionWhenAddPkpArgumentIsNull(mTestRule.getTestFramework(), false, false, false); 458 } 459 460 /** Tests that IllegalArgumentException is thrown if SHA1 is passed as the value of a pin. */ 461 @Test 462 @SmallTest testIllegalArgumentExceptionWhenPinValueIsSHA1()463 public void testIllegalArgumentExceptionWhenPinValueIsSHA1() throws Exception { 464 applyCronetEngineBuilderConfigurationPatch( 465 mTestRule.getTestFramework(), DISABLE_PINNING_BYPASS_FOR_LOCAL_ANCHORS); 466 byte[] sha1 = new byte[20]; 467 assertThrows( 468 "Pin value was: " + Arrays.toString(sha1), 469 IllegalArgumentException.class, 470 () -> 471 applyPkpSha256Patch( 472 mTestRule.getTestFramework(), 473 Http2TestServer.getServerHost(), 474 sha1, 475 EXCLUDE_SUBDOMAINS, 476 DISTANT_FUTURE)); 477 } 478 479 /** Asserts that the response from the server contains an PKP error. */ assertErrorResponse(TestUrlRequestCallback callback)480 private void assertErrorResponse(TestUrlRequestCallback callback) { 481 assertThat(callback.mError).isNotNull(); 482 // NetworkException#getCronetInternalErrorCode is exposed only by the native implementation. 483 if (mTestRule.implementationUnderTest() != CronetImplementation.STATICALLY_LINKED) { 484 return; 485 } 486 487 int errorCode = ((NetworkException) callback.mError).getCronetInternalErrorCode(); 488 Set<Integer> expectedErrors = new HashSet<>(); 489 expectedErrors.add(NetError.ERR_CONNECTION_REFUSED); 490 expectedErrors.add(NetError.ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN); 491 assertWithMessage( 492 String.format( 493 "Incorrect error code. Expected one of %s but received %s", 494 expectedErrors, errorCode)) 495 .that(expectedErrors) 496 .contains(errorCode); 497 } 498 499 /** Asserts a successful response with response code 200. */ assertSuccessfulResponse(TestUrlRequestCallback callback)500 private void assertSuccessfulResponse(TestUrlRequestCallback callback) { 501 if (callback.mError != null) { 502 fail( 503 "Did not expect an error but got error code " 504 + ((NetworkException) callback.mError).getCronetInternalErrorCode()); 505 } 506 assertWithMessage("Expected non-null response from the server") 507 .that(callback.getResponseInfoWithChecks()) 508 .isNotNull(); 509 assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200); 510 } 511 applyCronetEngineBuilderConfigurationPatch( CronetTestFramework testFramework, boolean bypassPinningForLocalAnchors)512 private static void applyCronetEngineBuilderConfigurationPatch( 513 CronetTestFramework testFramework, boolean bypassPinningForLocalAnchors) 514 throws Exception { 515 testFramework.applyEngineBuilderPatch( 516 (builder) -> configureCronetEngineBuilder(builder, bypassPinningForLocalAnchors)); 517 } 518 applyCronetEngineBuilderConfigurationPatchWithMockCertVerifier( CronetTestFramework testFramework, boolean bypassPinningForLocalAnchors)519 private static void applyCronetEngineBuilderConfigurationPatchWithMockCertVerifier( 520 CronetTestFramework testFramework, boolean bypassPinningForLocalAnchors) 521 throws Exception { 522 testFramework.applyEngineBuilderPatch( 523 (builder) -> 524 configureCronetEngineBuilderWithMockCertVerifier( 525 builder, bypassPinningForLocalAnchors)); 526 } 527 enableMockCertVerifier(ExperimentalCronetEngine.Builder builder)528 private static void enableMockCertVerifier(ExperimentalCronetEngine.Builder builder) 529 throws Exception { 530 final String[] server_certs = {SERVER_CERT_PEM}; 531 // knownRoot maps to net::CertVerifyResult.is_issued_by_known_root. There is no test which 532 // depends on that value as the only thing it affects is certificate verification for QUIC 533 // (where we never trust non web PKI certs, regardless of app/user config). Hence, always 534 // set this to false to maintain consistency with the non-MockCertVerifier case, where we 535 // use a non-trusted self signed certificate. 536 CronetTestUtil.setMockCertVerifierForTesting( 537 builder, 538 MockCertVerifier.createMockCertVerifier(server_certs, /* knownRoot= */ false)); 539 // MockCertVerifier uses certificates with hostname != localhost. So, setup fake 540 // hostname resolution. 541 JSONObject hostResolverParams = CronetTestUtil.generateHostResolverRules(); 542 JSONObject experimentalOptions = 543 new JSONObject().put("HostResolverRules", hostResolverParams); 544 builder.setExperimentalOptions(experimentalOptions.toString()); 545 } 546 internalConfigureCronetEngineBuilder( ExperimentalCronetEngine.Builder builder, boolean bypassPinningForLocalAnchors, boolean useMockCertVerifier)547 private static void internalConfigureCronetEngineBuilder( 548 ExperimentalCronetEngine.Builder builder, 549 boolean bypassPinningForLocalAnchors, 550 boolean useMockCertVerifier) 551 throws Exception { 552 builder.enablePublicKeyPinningBypassForLocalTrustAnchors(bypassPinningForLocalAnchors); 553 // TODO(crbug/1490552): When not explicitly enabled, fall back to MockCertVerifier if 554 // custom CAs are not supported. 555 if (useMockCertVerifier || Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { 556 enableMockCertVerifier(builder); 557 } 558 } 559 configureCronetEngineBuilder( ExperimentalCronetEngine.Builder builder, boolean bypassPinningForLocalAnchors)560 private static void configureCronetEngineBuilder( 561 ExperimentalCronetEngine.Builder builder, boolean bypassPinningForLocalAnchors) 562 throws Exception { 563 internalConfigureCronetEngineBuilder( 564 builder, bypassPinningForLocalAnchors, /* useMockCertVerifier= */ false); 565 } 566 configureCronetEngineBuilderWithMockCertVerifier( ExperimentalCronetEngine.Builder builder, boolean bypassPinningForLocalAnchors)567 private static void configureCronetEngineBuilderWithMockCertVerifier( 568 ExperimentalCronetEngine.Builder builder, boolean bypassPinningForLocalAnchors) 569 throws Exception { 570 internalConfigureCronetEngineBuilder( 571 builder, bypassPinningForLocalAnchors, /* useMockCertVerifier= */ true); 572 } 573 generateSomeSha256()574 private static byte[] generateSomeSha256() { 575 byte[] sha256 = new byte[32]; 576 Arrays.fill(sha256, (byte) 58); 577 return sha256; 578 } 579 580 @SuppressWarnings("ArrayAsKeyOfSetOrMap") applyPkpSha256Patch( CronetTestFramework testFramework, String host, byte[] pinHashValue, boolean includeSubdomain, int maxAgeInSec)581 private static void applyPkpSha256Patch( 582 CronetTestFramework testFramework, 583 String host, 584 byte[] pinHashValue, 585 boolean includeSubdomain, 586 int maxAgeInSec) { 587 Set<byte[]> hashes = new HashSet<>(); 588 hashes.add(pinHashValue); 589 testFramework.applyEngineBuilderPatch( 590 (builder) -> 591 builder.addPublicKeyPins( 592 host, hashes, includeSubdomain, dateInFuture(maxAgeInSec))); 593 } 594 readCertFromFileInPemFormat(String certFileName)595 private static X509Certificate readCertFromFileInPemFormat(String certFileName) 596 throws Exception { 597 byte[] certDer = CertTestUtil.pemToDer(CertTestUtil.CERTS_DIRECTORY + certFileName); 598 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 599 return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certDer)); 600 } 601 dateInFuture(int secondsIntoFuture)602 private static Date dateInFuture(int secondsIntoFuture) { 603 Calendar cal = Calendar.getInstance(); 604 cal.add(Calendar.SECOND, secondsIntoFuture); 605 return cal.getTime(); 606 } 607 assertNoExceptionWhenHostNameIsValid( CronetTestFramework testFramework, String hostName)608 private static void assertNoExceptionWhenHostNameIsValid( 609 CronetTestFramework testFramework, String hostName) { 610 try { 611 applyPkpSha256Patch( 612 testFramework, 613 hostName, 614 generateSomeSha256(), 615 INCLUDE_SUBDOMAINS, 616 DISTANT_FUTURE); 617 } catch (IllegalArgumentException ex) { 618 fail( 619 "Host name " 620 + hostName 621 + " should be valid but the exception was thrown: " 622 + ex.toString()); 623 } 624 } 625 assertExceptionWhenHostNameIsInvalid( CronetTestFramework testFramework, String hostName)626 private static void assertExceptionWhenHostNameIsInvalid( 627 CronetTestFramework testFramework, String hostName) { 628 assertThrows( 629 "Hostname was " + hostName, 630 IllegalArgumentException.class, 631 () -> 632 applyPkpSha256Patch( 633 testFramework, 634 hostName, 635 generateSomeSha256(), 636 INCLUDE_SUBDOMAINS, 637 DISTANT_FUTURE)); 638 } 639 640 @SuppressWarnings("ArrayAsKeyOfSetOrMap") verifyExceptionWhenAddPkpArgumentIsNull( CronetTestFramework testFramework, boolean hostNameIsNull, boolean pinsAreNull, boolean expirationDataIsNull)641 private static void verifyExceptionWhenAddPkpArgumentIsNull( 642 CronetTestFramework testFramework, 643 boolean hostNameIsNull, 644 boolean pinsAreNull, 645 boolean expirationDataIsNull) { 646 String hostName = hostNameIsNull ? null : "some-host.com"; 647 Set<byte[]> pins = pinsAreNull ? null : new HashSet<byte[]>(); 648 Date expirationDate = expirationDataIsNull ? null : new Date(); 649 650 boolean shouldThrowNpe = hostNameIsNull || pinsAreNull || expirationDataIsNull; 651 if (shouldThrowNpe) { 652 testFramework.applyEngineBuilderPatch( 653 (builder) -> 654 assertThrows( 655 NullPointerException.class, 656 () -> 657 builder.addPublicKeyPins( 658 hostName, 659 pins, 660 INCLUDE_SUBDOMAINS, 661 expirationDate))); 662 } else { 663 testFramework.applyEngineBuilderPatch( 664 (builder) -> 665 builder.addPublicKeyPins( 666 hostName, pins, INCLUDE_SUBDOMAINS, expirationDate)); 667 } 668 } 669 } 670