OLD | NEW |
---|---|
(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.Collection; | |
18 | |
19 /** | |
20 * Public-Key-Pinning tests of Cronet Java API. | |
xunjieli
2015/11/06 15:47:54
Should we say **dynamic** public key pinning here?
kapishnikov
2015/11/06 16:18:13
I would like to clarify the terminology here. I wa
xunjieli
2015/11/06 16:43:50
I got the impression that we do not want to do sta
davidben
2015/11/06 16:53:13
"static" vs "dynamic" is whatever we want to call
| |
21 */ | |
22 public class HpkpTest extends CronetTestBase { | |
23 private static final String CERT_USED = "quic_test.example.com.crt"; | |
24 private static final String[] CERTS_USED = {CERT_USED}; | |
25 | |
26 private CronetTestFramework mTestFramework; | |
27 private CronetEngine.Builder mBuilder; | |
28 private TestUrlRequestCallback mListener; | |
29 private String mServerUrl; // https://test.example.com:6121 | |
30 private String mServerHost; // test.example.com | |
31 private String mDomain; // example.com | |
32 | |
33 @Override | |
34 protected void setUp() throws Exception { | |
35 super.setUp(); | |
36 // Start QUIC Test Server | |
37 System.loadLibrary("cronet_tests"); | |
38 QuicTestServer.startQuicTestServer(getContext()); | |
39 mServerUrl = QuicTestServer.getServerURL(); | |
40 mServerHost = QuicTestServer.getServerHost(); | |
41 mDomain = mServerHost.substring(mServerHost.indexOf('.') + 1, mServerHos t.length()); | |
42 | |
43 // Set common CronetEngine parameters | |
44 mBuilder = new CronetEngine.Builder(getContext()); | |
45 mBuilder.enableQUIC(true); | |
46 mBuilder.addQuicHint(QuicTestServer.getServerHost(), QuicTestServer.getS erverPort(), | |
47 QuicTestServer.getServerPort()); | |
48 mBuilder.setStoragePath(CronetTestFramework.getTestStorage(getContext()) ); | |
49 mBuilder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 1 000 * 1024); | |
50 mBuilder.setMockCertVerifierForTesting(MockCertVerifier.createMockCertVe rifier(CERTS_USED)); | |
51 } | |
52 | |
53 @Override | |
54 protected void tearDown() throws Exception { | |
55 QuicTestServer.shutdownQuicTestServer(); | |
56 super.tearDown(); | |
57 } | |
58 | |
59 /** | |
60 * Tests the case when the pin hash does not match. The client is expected t o | |
61 * receive the error response. | |
62 * | |
63 * @throws Exception | |
64 */ | |
65 @SmallTest | |
66 @Feature({"Cronet"}) | |
67 public void testErrorCodeIfPinDoesNotMatch() throws Exception { | |
68 byte[] nonMatchingHash = generateSomeSha256(); | |
69 addPkpSha256(mServerHost, nonMatchingHash, false); | |
70 startCronetFramework(); | |
71 registerHostResolver(); | |
72 sendRequestAndWaitForResult(); | |
73 | |
74 assertErrorResponse(); | |
75 } | |
76 | |
77 /** | |
78 * Tests the case when the pin hash matches. The client is expected to | |
79 * receive the successful response with the response code 200. | |
80 * | |
81 * @throws Exception | |
82 */ | |
83 @SmallTest | |
84 @Feature({"Cronet"}) | |
85 public void testSuccessIfPinMatches() throws Exception { | |
86 // Get PKP hash of the real certificate | |
87 X509Certificate cert = readCertFromFileInPemFormat(CERT_USED); | |
88 byte[] matchingHash = CertTestUtil.getPublicKeySha256(cert); | |
89 | |
90 addPkpSha256(mServerHost, matchingHash, false); | |
91 startCronetFramework(); | |
92 registerHostResolver(); | |
93 sendRequestAndWaitForResult(); | |
94 | |
95 assertSuccessfulResponse(); | |
96 } | |
97 | |
98 /** | |
99 * Tests the case when the pin hash does not match and the client accesses t he subdomain of | |
100 * the configured HPKP host with includeSubdomains flag set to true. The cli ent is | |
101 * expected to receive the error response. | |
102 * | |
103 * @throws Exception | |
104 */ | |
105 @SmallTest | |
106 @Feature({"Cronet"}) | |
107 public void testIncludeSubdomainsFlagEqualTrue() throws Exception { | |
108 addPkpSha256(mDomain, generateSomeSha256(), true); | |
109 startCronetFramework(); | |
110 registerHostResolver(); | |
111 sendRequestAndWaitForResult(); | |
112 | |
113 assertErrorResponse(); | |
114 } | |
115 | |
116 /** | |
117 * Tests the case when the pin hash does not match and the client accesses t he subdomain of | |
118 * the configured HPKP host with includeSubdomains flag set to false. The cl ient is expected to | |
119 * receive the successful response with the response code 200. | |
120 * | |
121 * @throws Exception | |
122 */ | |
123 @SmallTest | |
124 @Feature({"Cronet"}) | |
125 public void testIncludeSubdomainsFlagEqualFalse() throws Exception { | |
126 addPkpSha256(mDomain, generateSomeSha256(), false); | |
127 startCronetFramework(); | |
128 registerHostResolver(); | |
129 sendRequestAndWaitForResult(); | |
130 | |
131 assertSuccessfulResponse(); | |
132 } | |
133 | |
134 /** | |
135 * Tests the case when the mismatching pin is set for some host that is diff erent from the one | |
136 * the client wants to access. In that case the other host pinning policy sh ould not be applied | |
137 * and the client is expected to receive the successful response with the re sponse code 200. | |
138 * | |
139 * @throws Exception | |
140 */ | |
141 @SmallTest | |
142 @Feature({"Cronet"}) | |
143 public void testSuccessIfNoPinSpecified() throws Exception { | |
144 addPkpSha256("somerandomhost.com", generateSomeSha256(), true); | |
145 startCronetFramework(); | |
146 registerHostResolver(); | |
147 sendRequestAndWaitForResult(); | |
148 | |
149 assertSuccessfulResponse(); | |
150 } | |
151 | |
152 /** | |
153 * Asserts that the response from the server contains an HPKP error. | |
154 * TODO(kapishnikov) currently QUIC returns ERR_QUIC_PROTOCOL_ERROR instead of expected | |
xunjieli
2015/11/06 15:47:54
nit: Probably only one TODO is needed. There shoul
kapishnikov
2015/11/06 16:18:13
Done.
| |
155 * TODO(kapishnikov) ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN error code when th e pin doesn't match. | |
156 * TODO(kapishnikov) This method should be changed when the bug is resolved. | |
157 * TODO(kapishnikov) See http://crbug.com/548378 | |
158 */ | |
159 private void assertErrorResponse() { | |
160 assertNotNull("Expected an error", mListener.mError); | |
161 int errorCode = mListener.mError.netError(); | |
162 boolean correctErrorCode = errorCode == NetError.ERR_SSL_PINNED_KEY_NOT_ IN_CERT_CHAIN | |
163 || errorCode == NetError.ERR_QUIC_PROTOCOL_ERROR; | |
164 assertTrue(String.format("Incorrect error code. Expected %s or %s but re ceived %s", | |
165 NetError.ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN, | |
166 NetError.ERR_QUIC_PROTOCOL_ERROR, errorCode), | |
167 correctErrorCode); | |
168 } | |
169 | |
170 /** | |
171 * Asserts a successful response with response code 200. | |
172 */ | |
173 private void assertSuccessfulResponse() { | |
174 if (mListener.mError != null) { | |
175 fail("Did not expect an error but got error code " + mListener.mErro r.mNetError); | |
176 } | |
177 assertNotNull("Expected non-null response from the server", mListener.mR esponseInfo); | |
178 assertEquals(200, mListener.mResponseInfo.getHttpStatusCode()); | |
179 } | |
180 | |
181 private void startCronetFramework() { | |
182 mTestFramework = startCronetTestFrameworkWithUrlAndCronetEngineBuilder(n ull, mBuilder); | |
183 } | |
184 | |
185 private void registerHostResolver() { | |
186 long urlRequestContextAdapter = ((CronetUrlRequestContext) mTestFramewor k.mCronetEngine) | |
187 .getUrlRequestContextAdapter(); | |
188 NativeTestServer.registerHostResolverProc(urlRequestContextAdapter, fals e); | |
189 } | |
190 | |
191 private byte[] generateSomeSha256() { | |
192 byte[] sha256 = new byte[32]; | |
193 Arrays.fill(sha256, (byte) 58); | |
194 return sha256; | |
195 } | |
196 | |
197 private void addPkpSha256(String host, byte[] pinHashValue, boolean includeS ubdomain) { | |
198 Collection<byte[]> hashes = new ArrayList<>(); | |
199 hashes.add(pinHashValue); | |
200 mBuilder.addPublicKeyPins(host, hashes, includeSubdomain); | |
201 } | |
202 | |
203 private void sendRequestAndWaitForResult() { | |
204 mListener = new TestUrlRequestCallback(); | |
205 | |
206 String quicURL = mServerUrl + "/simple.txt"; | |
207 UrlRequest.Builder requestBuilder = new UrlRequest.Builder( | |
208 quicURL, mListener, mListener.getExecutor(), mTestFramework.mCro netEngine); | |
209 requestBuilder.build().start(); | |
210 mListener.blockForDone(); | |
211 } | |
212 | |
213 private X509Certificate readCertFromFileInPemFormat(String certFileName) thr ows Exception { | |
214 byte[] certDer = CertTestUtil.pemToDer(CertTestUtil.CERTS_DIRECTORY + ce rtFileName); | |
215 CertificateFactory certFactory = CertificateFactory.getInstance("X.509") ; | |
216 return (X509Certificate) certFactory.generateCertificate(new ByteArrayIn putStream(certDer)); | |
217 } | |
218 } | |
OLD | NEW |