Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(754)

Side by Side Diff: components/cronet/android/test/javatests/src/org/chromium/net/PkpTest.java

Issue 1407263010: [Cronet] Public key pinning for Java API (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Hostname validation using IDN.USE_STD3_ASCII_RULES and conflict resolution Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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 package org.chromium.net;
6
7 import android.test.suitebuilder.annotation.SmallTest;
8
9 import org.chromium.base.test.util.Feature;
10 import org.chromium.net.test.util.CertTestUtil;
11
12 import java.io.ByteArrayInputStream;
13 import java.security.cert.CertificateFactory;
14 import java.security.cert.X509Certificate;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.Calendar;
18 import java.util.Collection;
19 import java.util.Date;
20
21 /**
22 * Public-Key-Pinning tests of Cronet Java API.
23 */
24 public class PkpTest extends CronetTestBase {
25 private static final String CERT_USED = "quic_test.example.com.crt";
26 private static final String[] CERTS_USED = {CERT_USED};
27 private static final int DISTANT_FUTURE = Integer.MAX_VALUE;
28 private static final boolean INCLUDE_SUBDOMAINS = true;
29 private static final boolean EXCLUDE_SUBDOMAINS = false;
30
31 private CronetTestFramework mTestFramework;
32 private CronetEngine.Builder mBuilder;
33 private TestUrlRequestCallback mListener;
34 private String mServerUrl; // https://test.example.com:6121
35 private String mServerHost; // test.example.com
36 private String mDomain; // example.com
37
38 @Override
39 protected void setUp() throws Exception {
40 super.setUp();
41 // Start QUIC Test Server
42 System.loadLibrary("cronet_tests");
43 QuicTestServer.startQuicTestServer(getContext());
44 mServerUrl = QuicTestServer.getServerURL();
45 mServerHost = QuicTestServer.getServerHost();
46 mDomain = mServerHost.substring(mServerHost.indexOf('.') + 1, mServerHos t.length());
47 createCronetEngineBuilder();
48 }
49
50 @Override
51 protected void tearDown() throws Exception {
52 QuicTestServer.shutdownQuicTestServer();
53 shutdownCronetEngine();
54 super.tearDown();
55 }
56
57 /**
58 * Tests the case when the pin hash does not match. The client is expected t o
59 * receive the error response.
60 *
61 * @throws Exception
62 */
63 @SmallTest
64 @Feature({"Cronet"})
65 public void testErrorCodeIfPinDoesNotMatch() throws Exception {
66 byte[] nonMatchingHash = generateSomeSha256();
67 addPkpSha256(mServerHost, nonMatchingHash, EXCLUDE_SUBDOMAINS, DISTANT_F UTURE);
nharper 2015/11/19 23:47:54 nit: calls to addPkpSha256 alternate between inlin
kapishnikov 2015/11/20 16:38:03 Done.
68 startCronetFramework();
69 registerHostResolver();
70 sendRequestAndWaitForResult();
71
72 assertErrorResponse();
73 }
74
75 /**
76 * Tests the case when the pin hash matches. The client is expected to
77 * receive the successful response with the response code 200.
78 *
79 * @throws Exception
80 */
81 @SmallTest
82 @Feature({"Cronet"})
83 public void testSuccessIfPinMatches() throws Exception {
84 // Get PKP hash of the real certificate
85 X509Certificate cert = readCertFromFileInPemFormat(CERT_USED);
86 byte[] matchingHash = CertTestUtil.getPublicKeySha256(cert);
87
88 addPkpSha256(mServerHost, matchingHash, EXCLUDE_SUBDOMAINS, DISTANT_FUTU RE);
89 startCronetFramework();
90 registerHostResolver();
91 sendRequestAndWaitForResult();
92
93 assertSuccessfulResponse();
94 }
95
96 /**
97 * Tests the case when the pin hash does not match and the client accesses t he subdomain of
98 * the configured PKP host with includeSubdomains flag set to true. The clie nt is
99 * expected to receive the error response.
100 *
101 * @throws Exception
102 */
103 @SmallTest
104 @Feature({"Cronet"})
105 public void testIncludeSubdomainsFlagEqualTrue() throws Exception {
106 addPkpSha256(mDomain, generateSomeSha256(), INCLUDE_SUBDOMAINS, DISTANT_ FUTURE);
107 startCronetFramework();
108 registerHostResolver();
109 sendRequestAndWaitForResult();
110
111 assertErrorResponse();
112 }
113
114 /**
115 * Tests the case when the pin hash does not match and the client accesses t he subdomain of
116 * the configured PKP host with includeSubdomains flag set to false. The cli ent is expected to
117 * receive the successful response with the response code 200.
118 *
119 * @throws Exception
120 */
121 @SmallTest
122 @Feature({"Cronet"})
123 public void testIncludeSubdomainsFlagEqualFalse() throws Exception {
124 addPkpSha256(mDomain, generateSomeSha256(), EXCLUDE_SUBDOMAINS, DISTANT_ FUTURE);
125 startCronetFramework();
126 registerHostResolver();
127 sendRequestAndWaitForResult();
128
129 assertSuccessfulResponse();
130 }
131
132 /**
133 * Tests the case when the mismatching pin is set for some host that is diff erent from the one
134 * the client wants to access. In that case the other host pinning policy sh ould not be applied
135 * and the client is expected to receive the successful response with the re sponse code 200.
136 *
137 * @throws Exception
138 */
139 @SmallTest
140 @Feature({"Cronet"})
141 public void testSuccessIfNoPinSpecified() throws Exception {
142 addPkpSha256("otherhost.com", generateSomeSha256(), INCLUDE_SUBDOMAINS, DISTANT_FUTURE);
143 startCronetFramework();
144 registerHostResolver();
145 sendRequestAndWaitForResult();
146
147 assertSuccessfulResponse();
148 }
149
150 /**
151 * Tests mismatching pins that will expire in 10 seconds. The pins should be still valid and
152 * enforced during the request; thus returning PIN mismatch error.
153 *
154 * @throws Exception
155 */
156 @SmallTest
157 @Feature({"Cronet"})
158 public void testSoonExpiringPin() throws Exception {
159 final int tenSecondsAhead = 10;
160 byte[] nonMatchingHash = generateSomeSha256();
161 addPkpSha256(mServerHost, nonMatchingHash, EXCLUDE_SUBDOMAINS, tenSecond sAhead);
162 startCronetFramework();
163 registerHostResolver();
164 sendRequestAndWaitForResult();
165
166 assertErrorResponse();
167 }
168
169 /**
170 * Tests mismatching pins that expired 1 second ago. Since the pins have exp ired, they
171 * should not be enforced during the request; thus a successful response is expected.
172 *
173 * @throws Exception
174 */
175 @SmallTest
176 @Feature({"Cronet"})
177 public void testRecentlyExpiredPin() throws Exception {
178 final int oneSecondAgo = -1;
179 byte[] nonMatchingHash = generateSomeSha256();
180 addPkpSha256(mServerHost, nonMatchingHash, EXCLUDE_SUBDOMAINS, oneSecond Ago);
181 startCronetFramework();
182 registerHostResolver();
183 sendRequestAndWaitForResult();
184
185 assertSuccessfulResponse();
186 }
187
188 /**
189 * Tests that host pinning is not persisted between multiple CronetEngine in stances.
190 *
191 * @throws Exception
192 */
193 @SmallTest
194 @Feature({"Cronet"})
195 public void testPinsAreNotPersisted() throws Exception {
196 byte[] nonMatchingHash = generateSomeSha256();
197 addPkpSha256(mServerHost, nonMatchingHash, EXCLUDE_SUBDOMAINS, DISTANT_F UTURE);
198 startCronetFramework();
199 registerHostResolver();
200 sendRequestAndWaitForResult();
201 assertErrorResponse();
202 shutdownCronetEngine();
203
204 // Restart Cronet engine and try the same request again. Since the pins are not persisted,
205 // a successful response is expected.
206 createCronetEngineBuilder();
207 startCronetFramework();
208 registerHostResolver();
209 sendRequestAndWaitForResult();
210 assertSuccessfulResponse();
211 }
212
213 /**
214 * Tests that the client receives {@code InvalidArgumentException} when the pinned host name
215 * is invalid.
216 *
217 * @throws Exception
218 */
219 @SmallTest
220 @Feature({"Cronet"})
221 public void testHostNameArgumentValidation() throws Exception {
222 assertExceptionWhenHostNameIsInvalid("http://domain.com");
223 assertExceptionWhenHostNameIsInvalid("domain.com:4000");
224 assertExceptionWhenHostNameIsInvalid("domain.com/mydoc.txt");
225 assertExceptionWhenHostNameIsInvalid("128.55.0.1");
226 }
227
228 /**
229 * Asserts that the response from the server contains an PKP error.
230 * TODO(kapishnikov): currently QUIC returns ERR_QUIC_PROTOCOL_ERROR instead of expected
231 * ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN error code when the pin doesn't matc h.
232 * This method should be changed when the bug is resolved. See http://crbug. com/548378
233 */
234 private void assertErrorResponse() {
235 assertNotNull("Expected an error", mListener.mError);
236 int errorCode = mListener.mError.netError();
237 boolean correctErrorCode = errorCode == NetError.ERR_SSL_PINNED_KEY_NOT_ IN_CERT_CHAIN
238 || errorCode == NetError.ERR_QUIC_PROTOCOL_ERROR;
239 assertTrue(String.format("Incorrect error code. Expected %s or %s but re ceived %s",
240 NetError.ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN,
241 NetError.ERR_QUIC_PROTOCOL_ERROR, errorCode),
242 correctErrorCode);
243 }
244
245 /**
246 * Asserts a successful response with response code 200.
247 */
248 private void assertSuccessfulResponse() {
249 if (mListener.mError != null) {
250 fail("Did not expect an error but got error code " + mListener.mErro r.mNetError);
251 }
252 assertNotNull("Expected non-null response from the server", mListener.mR esponseInfo);
253 assertEquals(200, mListener.mResponseInfo.getHttpStatusCode());
254 }
255
256 private void createCronetEngineBuilder() {
257 // Set common CronetEngine parameters
258 mBuilder = new CronetEngine.Builder(getContext());
259 mBuilder.enableQUIC(true);
260 mBuilder.addQuicHint(QuicTestServer.getServerHost(), QuicTestServer.getS erverPort(),
261 QuicTestServer.getServerPort());
262 mBuilder.setStoragePath(CronetTestFramework.getTestStorage(getContext()) );
263 mBuilder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 1 000 * 1024);
264 mBuilder.setMockCertVerifierForTesting(MockCertVerifier.createMockCertVe rifier(CERTS_USED));
265 }
266
267 private void startCronetFramework() {
268 mTestFramework = startCronetTestFrameworkWithUrlAndCronetEngineBuilder(n ull, mBuilder);
269 }
270
271 private void shutdownCronetEngine() {
272 if (mTestFramework != null && mTestFramework.mCronetEngine != null) {
273 mTestFramework.mCronetEngine.shutdown();
274 }
275 }
276
277 private void registerHostResolver() {
278 long urlRequestContextAdapter = ((CronetUrlRequestContext) mTestFramewor k.mCronetEngine)
279 .getUrlRequestContextAdapter();
280 NativeTestServer.registerHostResolverProc(urlRequestContextAdapter, fals e);
281 }
282
283 private byte[] generateSomeSha256() {
284 byte[] sha256 = new byte[32];
285 Arrays.fill(sha256, (byte) 58);
286 return sha256;
287 }
288
289 private void addPkpSha256(
290 String host, byte[] pinHashValue, boolean includeSubdomain, int maxA geInSec) {
291 Collection<byte[]> hashes = new ArrayList<>();
292 hashes.add(pinHashValue);
293 mBuilder.addPublicKeyPins(host, hashes, includeSubdomain, dateInFuture(m axAgeInSec));
294 }
295
296 private void sendRequestAndWaitForResult() {
297 mListener = new TestUrlRequestCallback();
298
299 String quicURL = mServerUrl + "/simple.txt";
300 UrlRequest.Builder requestBuilder = new UrlRequest.Builder(
301 quicURL, mListener, mListener.getExecutor(), mTestFramework.mCro netEngine);
302 requestBuilder.build().start();
303 mListener.blockForDone();
304 }
305
306 private X509Certificate readCertFromFileInPemFormat(String certFileName) thr ows Exception {
307 byte[] certDer = CertTestUtil.pemToDer(CertTestUtil.CERTS_DIRECTORY + ce rtFileName);
308 CertificateFactory certFactory = CertificateFactory.getInstance("X.509") ;
309 return (X509Certificate) certFactory.generateCertificate(new ByteArrayIn putStream(certDer));
310 }
311
312 private Date dateInFuture(int secondsIntoFuture) {
313 Calendar cal = Calendar.getInstance();
314 cal.add(Calendar.SECOND, secondsIntoFuture);
315 return cal.getTime();
316 }
317
318 private void assertExceptionWhenHostNameIsInvalid(String hostName) {
319 try {
320 addPkpSha256(hostName, generateSomeSha256(), INCLUDE_SUBDOMAINS, DIS TANT_FUTURE);
321 } catch (IllegalArgumentException ex) {
322 // Expected exception.
323 return;
324 }
325 fail("Expected IllegalArgumentException when passing " + hostName + " ho st name");
326 }
327 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698