• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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