1 /* |
|
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. |
|
8 * |
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
12 * version 2 for more details (a copy is included in the LICENSE file that |
|
13 * accompanied this code). |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License version |
|
16 * 2 along with this work; if not, write to the Free Software Foundation, |
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
18 * |
|
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
20 * or visit www.oracle.com if you need additional information or have any |
|
21 * questions. |
|
22 */ |
|
23 |
|
24 // SunJSSE does not support dynamic system properties, no way to re-use |
|
25 // system properties in samevm/agentvm mode. |
|
26 |
|
27 // See ../../../../RunStatReqSelect.java for the jtreg header |
|
28 |
|
29 package sun.security.ssl; |
|
30 |
|
31 import javax.net.ssl.*; |
|
32 import javax.net.ssl.SSLEngineResult.*; |
|
33 import javax.security.auth.x500.X500Principal; |
|
34 import java.io.*; |
|
35 import java.math.BigInteger; |
|
36 import java.security.*; |
|
37 import java.nio.*; |
|
38 import java.security.cert.X509Certificate; |
|
39 import java.security.cert.Extension; |
|
40 import java.util.ArrayList; |
|
41 import java.util.Collections; |
|
42 import java.util.Date; |
|
43 import java.util.HashMap; |
|
44 import java.util.List; |
|
45 import java.util.Map; |
|
46 import java.util.Objects; |
|
47 import java.util.concurrent.TimeUnit; |
|
48 |
|
49 import sun.security.provider.certpath.OCSPNonceExtension; |
|
50 import sun.security.provider.certpath.ResponderId; |
|
51 import sun.security.testlibrary.SimpleOCSPServer; |
|
52 import sun.security.testlibrary.CertificateBuilder; |
|
53 |
|
54 public class StatusReqSelection { |
|
55 |
|
56 /* |
|
57 * Enables logging of the SSLEngine operations. |
|
58 */ |
|
59 private static final boolean logging = true; |
|
60 |
|
61 /* |
|
62 * Enables the JSSE system debugging system property: |
|
63 * |
|
64 * -Djavax.net.debug=all |
|
65 * |
|
66 * This gives a lot of low-level information about operations underway, |
|
67 * including specific handshake messages, and might be best examined |
|
68 * after gaining some familiarity with this application. |
|
69 */ |
|
70 private static final boolean debug = false; |
|
71 |
|
72 // The following items are used to set up the keystores. |
|
73 private static final String passwd = "passphrase"; |
|
74 private static final String ROOT_ALIAS = "root"; |
|
75 private static final String INT_ALIAS = "intermediate"; |
|
76 private static final String SSL_ALIAS = "ssl"; |
|
77 |
|
78 // PKI and server components we will need for this test |
|
79 private static KeyManagerFactory kmf; |
|
80 private static TrustManagerFactory tmf; |
|
81 private static KeyStore rootKeystore; // Root CA Keystore |
|
82 private static KeyStore intKeystore; // Intermediate CA Keystore |
|
83 private static KeyStore serverKeystore; // SSL Server Keystore |
|
84 private static KeyStore trustStore; // SSL Client trust store |
|
85 private static SimpleOCSPServer rootOcsp; // Root CA OCSP Responder |
|
86 private static int rootOcspPort; // Port for root OCSP |
|
87 private static SimpleOCSPServer intOcsp; // Intermediate CA OCSP server |
|
88 private static int intOcspPort; // Port for intermediate OCSP |
|
89 private static SSLContext ctxStaple; // SSLContext for all tests |
|
90 |
|
91 // Some useful objects we will need for test purposes |
|
92 private static final SecureRandom RNG = new SecureRandom(); |
|
93 |
|
94 // We'll be using these objects repeatedly to make hello messages |
|
95 private static final ProtocolVersion VER_1_0 = ProtocolVersion.TLS10; |
|
96 private static final ProtocolVersion VER_1_2 = ProtocolVersion.TLS12; |
|
97 private static final CipherSuiteList SUITES = new CipherSuiteList( |
|
98 CipherSuite.valueOf("TLS_RSA_WITH_AES_128_GCM_SHA256")); |
|
99 private static final SessionId SID = new SessionId(new byte[0]); |
|
100 private static final HelloExtension RNIEXT = |
|
101 new RenegotiationInfoExtension(new byte[0], new byte[0]); |
|
102 private static final List<SignatureAndHashAlgorithm> algList = |
|
103 new ArrayList<SignatureAndHashAlgorithm>() {{ |
|
104 add(SignatureAndHashAlgorithm.valueOf(4, 1, 0)); |
|
105 }}; // List with only SHA256withRSA |
|
106 private static final SignatureAlgorithmsExtension SIGALGEXT = |
|
107 new SignatureAlgorithmsExtension(algList); |
|
108 |
|
109 /* |
|
110 * Main entry point for this test. |
|
111 */ |
|
112 public static void main(String args[]) throws Exception { |
|
113 int testsPassed = 0; |
|
114 |
|
115 if (debug) { |
|
116 System.setProperty("javax.net.debug", "ssl"); |
|
117 } |
|
118 |
|
119 // All tests will have stapling enabled on the server side |
|
120 System.setProperty("jdk.tls.server.enableStatusRequestExtension", |
|
121 "true"); |
|
122 |
|
123 // Create a single SSLContext that we can use for all tests |
|
124 ctxStaple = SSLContext.getInstance("TLS"); |
|
125 |
|
126 // Create the PKI we will use for the test and start the OCSP servers |
|
127 createPKI(); |
|
128 |
|
129 // Set up the KeyManagerFactory and TrustManagerFactory |
|
130 kmf = KeyManagerFactory.getInstance("PKIX"); |
|
131 kmf.init(serverKeystore, passwd.toCharArray()); |
|
132 tmf = TrustManagerFactory.getInstance("PKIX"); |
|
133 tmf.init(trustStore); |
|
134 |
|
135 List<TestCase> testList = new ArrayList<TestCase>() {{ |
|
136 add(new TestCase("ClientHello: No stapling extensions", |
|
137 makeHelloNoStaplingExts(), false, false)); |
|
138 add(new TestCase("ClientHello: Default status_request only", |
|
139 makeDefaultStatReqOnly(), true, false)); |
|
140 add(new TestCase("ClientHello: Default status_request_v2 only", |
|
141 makeDefaultStatReqV2Only(), false, true)); |
|
142 add(new TestCase("ClientHello: Both status_request exts, default", |
|
143 makeDefaultStatReqBoth(), false, true)); |
|
144 add(new TestCase( |
|
145 "ClientHello: Hello with status_request and responder IDs", |
|
146 makeStatReqWithRid(), false, false)); |
|
147 add(new TestCase( |
|
148 "ClientHello: Hello with status_request using no " + |
|
149 "responder IDs but provides the OCSP nonce extension", |
|
150 makeStatReqNoRidNonce(), true, false)); |
|
151 add(new TestCase("ClientHello with default status_request and " + |
|
152 "status_request_v2 with ResponderIds", |
|
153 makeStatReqDefV2WithRid(), true, false)); |
|
154 add(new TestCase("ClientHello with default status_request and " + |
|
155 "status_request_v2 (OCSP_MULTI with ResponderId, " + |
|
156 "OCSP as a default request)", |
|
157 makeStatReqDefV2MultiWithRidSingleDef(), false, true)); |
|
158 add(new TestCase("ClientHello with status_request and " + |
|
159 "status_request_v2 and all OCSPStatusRequests use " + |
|
160 "Responder IDs", |
|
161 makeStatReqAllWithRid(), false, false)); |
|
162 add(new TestCase("ClientHello with default status_request and " + |
|
163 "status_request_v2 that has a default OCSP item and " + |
|
164 "multiple OCSP_MULTI items, only one is default", |
|
165 makeHelloMultiV2andSingle(), false, true)); |
|
166 }}; |
|
167 |
|
168 // Run the client and server property tests |
|
169 for (TestCase test : testList) { |
|
170 try { |
|
171 log("*** Test: " + test.testName); |
|
172 if (runTest(test)) { |
|
173 log("PASS: status_request: " + test.statReqEnabled + |
|
174 ", status_request_v2: " + test.statReqV2Enabled); |
|
175 testsPassed++; |
|
176 } |
|
177 } catch (Exception e) { |
|
178 // If we get an exception, we'll count it as a failure |
|
179 log("Test failure due to exception: " + e); |
|
180 } |
|
181 log(""); |
|
182 } |
|
183 |
|
184 // Summary |
|
185 if (testsPassed != testList.size()) { |
|
186 throw new RuntimeException(testList.size() - testsPassed + |
|
187 " tests failed out of " + testList.size() + " total."); |
|
188 } else { |
|
189 log("Total tests: " + testList.size() + ", all passed"); |
|
190 } |
|
191 } |
|
192 |
|
193 private static boolean runTest(TestCase test) throws Exception { |
|
194 SSLEngineResult serverResult; |
|
195 |
|
196 // Create a Server SSLEngine to receive our customized ClientHello |
|
197 ctxStaple.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); |
|
198 SSLEngine engine = ctxStaple.createSSLEngine(); |
|
199 engine.setUseClientMode(false); |
|
200 engine.setNeedClientAuth(false); |
|
201 |
|
202 SSLSession session = engine.getSession(); |
|
203 ByteBuffer serverOut = ByteBuffer.wrap("I'm a Server".getBytes()); |
|
204 ByteBuffer serverIn = |
|
205 ByteBuffer.allocate(session.getApplicationBufferSize() + 50); |
|
206 ByteBuffer sTOc = |
|
207 ByteBuffer.allocateDirect(session.getPacketBufferSize()); |
|
208 |
|
209 // Send the ClientHello ByteBuffer in the test case |
|
210 if (debug) { |
|
211 System.out.println("Sending Client Hello:\n" + |
|
212 dumpHexBytes(test.data)); |
|
213 } |
|
214 |
|
215 // Consume the client hello |
|
216 serverResult = engine.unwrap(test.data, serverIn); |
|
217 if (debug) { |
|
218 log("server unwrap: ", serverResult); |
|
219 } |
|
220 if (serverResult.getStatus() != SSLEngineResult.Status.OK) { |
|
221 throw new SSLException("Server unwrap got status: " + |
|
222 serverResult.getStatus()); |
|
223 } else if (serverResult.getHandshakeStatus() != |
|
224 SSLEngineResult.HandshakeStatus.NEED_TASK) { |
|
225 throw new SSLException("Server unwrap expected NEED_TASK, got: " + |
|
226 serverResult.getHandshakeStatus()); |
|
227 } |
|
228 runDelegatedTasks(serverResult, engine); |
|
229 if (engine.getHandshakeStatus() != |
|
230 SSLEngineResult.HandshakeStatus.NEED_WRAP) { |
|
231 throw new SSLException("Expected NEED_WRAP, got: " + |
|
232 engine.getHandshakeStatus()); |
|
233 } |
|
234 |
|
235 // Generate a TLS record with the ServerHello |
|
236 serverResult = engine.wrap(serverOut, sTOc); |
|
237 if (debug) { |
|
238 log("client wrap: ", serverResult); |
|
239 } |
|
240 if (serverResult.getStatus() != SSLEngineResult.Status.OK) { |
|
241 throw new SSLException("Client wrap got status: " + |
|
242 serverResult.getStatus()); |
|
243 } |
|
244 sTOc.flip(); |
|
245 |
|
246 if (debug) { |
|
247 log("Server Response:\n" + dumpHexBytes(sTOc)); |
|
248 } |
|
249 |
|
250 return checkServerHello(sTOc, test.statReqEnabled, |
|
251 test.statReqV2Enabled); |
|
252 } |
|
253 |
|
254 /** |
|
255 * Make a TLSv1.2 ClientHello with only RNI and no stapling extensions |
|
256 */ |
|
257 private static ByteBuffer makeHelloNoStaplingExts() throws IOException { |
|
258 // Craft the ClientHello byte buffer |
|
259 HelloExtensions exts = new HelloExtensions(); |
|
260 exts.add(RNIEXT); |
|
261 exts.add(SIGALGEXT); |
|
262 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
263 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
264 } |
|
265 |
|
266 /** |
|
267 * Make a TLSv1.2 ClientHello with the RNI and Status Request extensions |
|
268 */ |
|
269 private static ByteBuffer makeDefaultStatReqOnly() throws IOException { |
|
270 // Craft the ClientHello byte buffer |
|
271 HelloExtensions exts = new HelloExtensions(); |
|
272 exts.add(RNIEXT); |
|
273 exts.add(SIGALGEXT); |
|
274 exts.add(new CertStatusReqExtension(StatusRequestType.OCSP, |
|
275 new OCSPStatusRequest(null, null))); |
|
276 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
277 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
278 } |
|
279 |
|
280 /** |
|
281 * Make a TLSv1.2 ClientHello with the RNI and Status Request V2 extension |
|
282 */ |
|
283 private static ByteBuffer makeDefaultStatReqV2Only() throws IOException { |
|
284 // Craft the ClientHello byte buffer |
|
285 HelloExtensions exts = new HelloExtensions(); |
|
286 OCSPStatusRequest osr = new OCSPStatusRequest(); |
|
287 List<CertStatusReqItemV2> itemList = new ArrayList<>(2); |
|
288 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI, |
|
289 osr)); |
|
290 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP, osr)); |
|
291 |
|
292 exts.add(RNIEXT); |
|
293 exts.add(SIGALGEXT); |
|
294 exts.add(new CertStatusReqListV2Extension(itemList)); |
|
295 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
296 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
297 } |
|
298 /** |
|
299 * Make a TLSv1.2 ClientHello with Status Request and Status Request V2 |
|
300 * extensions. |
|
301 */ |
|
302 private static ByteBuffer makeDefaultStatReqBoth() throws IOException { |
|
303 // Craft the ClientHello byte buffer |
|
304 HelloExtensions exts = new HelloExtensions(); |
|
305 OCSPStatusRequest osr = new OCSPStatusRequest(); |
|
306 List<CertStatusReqItemV2> itemList = new ArrayList<>(2); |
|
307 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI, |
|
308 osr)); |
|
309 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP, osr)); |
|
310 |
|
311 exts.add(RNIEXT); |
|
312 exts.add(SIGALGEXT); |
|
313 exts.add(new CertStatusReqExtension(StatusRequestType.OCSP, |
|
314 new OCSPStatusRequest(null, null))); |
|
315 exts.add(new CertStatusReqListV2Extension(itemList)); |
|
316 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
317 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
318 } |
|
319 |
|
320 /** |
|
321 * Make a ClientHello using a status_request that has a single |
|
322 * responder ID in it. |
|
323 */ |
|
324 private static ByteBuffer makeStatReqWithRid() throws IOException { |
|
325 HelloExtensions exts = new HelloExtensions(); |
|
326 exts.add(RNIEXT); |
|
327 exts.add(SIGALGEXT); |
|
328 List<ResponderId> rids = new ArrayList<ResponderId>() {{ |
|
329 add(new ResponderId(new X500Principal("CN=Foo"))); |
|
330 }}; |
|
331 exts.add(new CertStatusReqExtension(StatusRequestType.OCSP, |
|
332 new OCSPStatusRequest(rids, null))); |
|
333 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
334 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
335 } |
|
336 |
|
337 /** |
|
338 * Make a ClientHello using a status_request that has no |
|
339 * responder IDs but does provide the nonce extension. |
|
340 */ |
|
341 private static ByteBuffer makeStatReqNoRidNonce() throws IOException { |
|
342 HelloExtensions exts = new HelloExtensions(); |
|
343 exts.add(RNIEXT); |
|
344 exts.add(SIGALGEXT); |
|
345 List<Extension> ocspExts = new ArrayList<Extension>() {{ |
|
346 add(new OCSPNonceExtension(16)); |
|
347 }}; |
|
348 exts.add(new CertStatusReqExtension(StatusRequestType.OCSP, |
|
349 new OCSPStatusRequest(null, ocspExts))); |
|
350 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
351 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
352 } |
|
353 |
|
354 /** |
|
355 * Make a ClientHello using a default status_request and a |
|
356 * status_request_v2 that has a single responder ID in it. |
|
357 */ |
|
358 private static ByteBuffer makeStatReqDefV2WithRid() throws IOException { |
|
359 HelloExtensions exts = new HelloExtensions(); |
|
360 List<ResponderId> rids = new ArrayList<ResponderId>() {{ |
|
361 add(new ResponderId(new X500Principal("CN=Foo"))); |
|
362 }}; |
|
363 List<CertStatusReqItemV2> itemList = new ArrayList<>(2); |
|
364 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI, |
|
365 new OCSPStatusRequest(rids, null))); |
|
366 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP, |
|
367 new OCSPStatusRequest(rids, null))); |
|
368 |
|
369 exts.add(RNIEXT); |
|
370 exts.add(SIGALGEXT); |
|
371 exts.add(new CertStatusReqExtension(StatusRequestType.OCSP, |
|
372 new OCSPStatusRequest(null, null))); |
|
373 exts.add(new CertStatusReqListV2Extension(itemList)); |
|
374 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
375 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
376 } |
|
377 |
|
378 /** |
|
379 * Make a ClientHello using a default status_request and a |
|
380 * status_request_v2 that has a single responder ID in it for the |
|
381 * OCSP_MULTI request item and a default OCSP request item. |
|
382 */ |
|
383 private static ByteBuffer makeStatReqDefV2MultiWithRidSingleDef() |
|
384 throws IOException { |
|
385 HelloExtensions exts = new HelloExtensions(); |
|
386 List<ResponderId> rids = new ArrayList<ResponderId>() {{ |
|
387 add(new ResponderId(new X500Principal("CN=Foo"))); |
|
388 }}; |
|
389 List<CertStatusReqItemV2> itemList = new ArrayList<>(2); |
|
390 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI, |
|
391 new OCSPStatusRequest(rids, null))); |
|
392 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP, |
|
393 new OCSPStatusRequest(null, null))); |
|
394 |
|
395 exts.add(RNIEXT); |
|
396 exts.add(SIGALGEXT); |
|
397 exts.add(new CertStatusReqExtension(StatusRequestType.OCSP, |
|
398 new OCSPStatusRequest(null, null))); |
|
399 exts.add(new CertStatusReqListV2Extension(itemList)); |
|
400 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
401 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
402 } |
|
403 |
|
404 /** |
|
405 * Make a ClientHello using status_request and status_request_v2 where |
|
406 * all underlying OCSPStatusRequests use responder IDs. |
|
407 */ |
|
408 private static ByteBuffer makeStatReqAllWithRid() throws IOException { |
|
409 HelloExtensions exts = new HelloExtensions(); |
|
410 List<ResponderId> rids = new ArrayList<ResponderId>() {{ |
|
411 add(new ResponderId(new X500Principal("CN=Foo"))); |
|
412 }}; |
|
413 List<CertStatusReqItemV2> itemList = new ArrayList<>(2); |
|
414 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI, |
|
415 new OCSPStatusRequest(rids, null))); |
|
416 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP, |
|
417 new OCSPStatusRequest(rids, null))); |
|
418 |
|
419 exts.add(RNIEXT); |
|
420 exts.add(SIGALGEXT); |
|
421 exts.add(new CertStatusReqExtension(StatusRequestType.OCSP, |
|
422 new OCSPStatusRequest(rids, null))); |
|
423 exts.add(new CertStatusReqListV2Extension(itemList)); |
|
424 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
425 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
426 } |
|
427 |
|
428 /** |
|
429 * Make a TLSv1.2 ClientHello multiple CertStatusReqItemV2s of different |
|
430 * types. One of the middle items should be acceptable while the others |
|
431 * have responder IDs. The status_request (v1) should also be acceptable |
|
432 * but should be overridden in favor of the status_request_v2. |
|
433 */ |
|
434 private static ByteBuffer makeHelloMultiV2andSingle() throws IOException { |
|
435 // Craft the ClientHello byte buffer |
|
436 HelloExtensions exts = new HelloExtensions(); |
|
437 List<ResponderId> fooRid = Collections.singletonList( |
|
438 new ResponderId(new X500Principal("CN=Foo"))); |
|
439 List<ResponderId> barRid = Collections.singletonList( |
|
440 new ResponderId(new X500Principal("CN=Bar"))); |
|
441 List<CertStatusReqItemV2> itemList = new ArrayList<>(); |
|
442 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP, |
|
443 new OCSPStatusRequest(null, null))); |
|
444 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI, |
|
445 new OCSPStatusRequest(fooRid, null))); |
|
446 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI, |
|
447 new OCSPStatusRequest(null, null))); |
|
448 itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI, |
|
449 new OCSPStatusRequest(barRid, null))); |
|
450 |
|
451 exts.add(RNIEXT); |
|
452 exts.add(SIGALGEXT); |
|
453 exts.add(new CertStatusReqExtension(StatusRequestType.OCSP, |
|
454 new OCSPStatusRequest(null, null))); |
|
455 exts.add(new CertStatusReqListV2Extension(itemList)); |
|
456 return createTlsRecord(Record.ct_handshake, VER_1_2, |
|
457 createClientHelloMsg(VER_1_2, SID, SUITES, exts)); |
|
458 } |
|
459 |
|
460 /** |
|
461 * Wrap a TLS content message into a TLS record header |
|
462 * |
|
463 * @param contentType a byte containing the content type value |
|
464 * @param pv the protocol version for this record |
|
465 * @param data a byte buffer containing the message data |
|
466 * @return |
|
467 */ |
|
468 private static ByteBuffer createTlsRecord(byte contentType, |
|
469 ProtocolVersion pv, ByteBuffer data) { |
|
470 int msgLen = (data != null) ? data.limit() : 0; |
|
471 |
|
472 // Allocate enough space to hold the TLS record header + the message |
|
473 ByteBuffer recordBuf = ByteBuffer.allocate(msgLen + 5); |
|
474 recordBuf.put(contentType); |
|
475 recordBuf.putShort((short)pv.v); |
|
476 recordBuf.putShort((short)msgLen); |
|
477 if (msgLen > 0) { |
|
478 recordBuf.put(data); |
|
479 } |
|
480 |
|
481 recordBuf.flip(); |
|
482 return recordBuf; |
|
483 } |
|
484 |
|
485 /** |
|
486 * Craft and encode a ClientHello message as a byte array. |
|
487 * |
|
488 * @param pv the protocol version asserted in the hello message. |
|
489 * @param sessId the session ID for this hello message. |
|
490 * @param suites a list consisting of one or more cipher suite objects |
|
491 * @param extensions a list of HelloExtension objects |
|
492 * |
|
493 * @return a byte array containing the encoded ClientHello message. |
|
494 */ |
|
495 private static ByteBuffer createClientHelloMsg(ProtocolVersion pv, |
|
496 SessionId sessId, CipherSuiteList suites, |
|
497 HelloExtensions extensions) throws IOException { |
|
498 ByteBuffer msgBuf; |
|
499 |
|
500 HandshakeOutStream hsos = |
|
501 new HandshakeOutStream(new SSLEngineOutputRecord()); |
|
502 |
|
503 // Construct the client hello object from the first 3 parameters |
|
504 HandshakeMessage.ClientHello cHello = |
|
505 new HandshakeMessage.ClientHello(RNG, pv, sessId, suites, |
|
506 false); |
|
507 |
|
508 // Use the HelloExtensions provided by the caller |
|
509 if (extensions != null) { |
|
510 cHello.extensions = extensions; |
|
511 } |
|
512 |
|
513 cHello.send(hsos); |
|
514 msgBuf = ByteBuffer.allocate(hsos.size() + 4); |
|
515 |
|
516 // Combine the handshake type with the length |
|
517 msgBuf.putInt((HandshakeMessage.ht_client_hello << 24) | |
|
518 (hsos.size() & 0x00FFFFFF)); |
|
519 msgBuf.put(hsos.toByteArray()); |
|
520 msgBuf.flip(); |
|
521 return msgBuf; |
|
522 } |
|
523 |
|
524 /* |
|
525 * If the result indicates that we have outstanding tasks to do, |
|
526 * go ahead and run them in this thread. |
|
527 */ |
|
528 private static void runDelegatedTasks(SSLEngineResult result, |
|
529 SSLEngine engine) throws Exception { |
|
530 |
|
531 if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { |
|
532 Runnable runnable; |
|
533 while ((runnable = engine.getDelegatedTask()) != null) { |
|
534 if (debug) { |
|
535 log("\trunning delegated task..."); |
|
536 } |
|
537 runnable.run(); |
|
538 } |
|
539 HandshakeStatus hsStatus = engine.getHandshakeStatus(); |
|
540 if (hsStatus == HandshakeStatus.NEED_TASK) { |
|
541 throw new Exception( |
|
542 "handshake shouldn't need additional tasks"); |
|
543 } |
|
544 if (debug) { |
|
545 log("\tnew HandshakeStatus: " + hsStatus); |
|
546 } |
|
547 } |
|
548 } |
|
549 |
|
550 private static void log(String str, SSLEngineResult result) { |
|
551 if (!logging) { |
|
552 return; |
|
553 } |
|
554 HandshakeStatus hsStatus = result.getHandshakeStatus(); |
|
555 log(str + |
|
556 result.getStatus() + "/" + hsStatus + ", " + |
|
557 result.bytesConsumed() + "/" + result.bytesProduced() + |
|
558 " bytes"); |
|
559 if (hsStatus == HandshakeStatus.FINISHED) { |
|
560 log("\t...ready for application data"); |
|
561 } |
|
562 } |
|
563 |
|
564 private static void log(String str) { |
|
565 if (logging) { |
|
566 System.out.println(str); |
|
567 } |
|
568 } |
|
569 |
|
570 /** |
|
571 * Dump a ByteBuffer as a hexdump to stdout. The dumping routine will |
|
572 * start at the current position of the buffer and run to its limit. |
|
573 * After completing the dump, the position will be returned to its |
|
574 * starting point. |
|
575 * |
|
576 * @param data the ByteBuffer to dump to stdout. |
|
577 * |
|
578 * @return the hexdump of the byte array. |
|
579 */ |
|
580 private static String dumpHexBytes(ByteBuffer data) { |
|
581 StringBuilder sb = new StringBuilder(); |
|
582 if (data != null) { |
|
583 int i = 0; |
|
584 data.mark(); |
|
585 while (data.hasRemaining()) { |
|
586 if (i % 16 == 0 && i != 0) { |
|
587 sb.append("\n"); |
|
588 } |
|
589 sb.append(String.format("%02X ", data.get())); |
|
590 i++; |
|
591 } |
|
592 data.reset(); |
|
593 } |
|
594 |
|
595 return sb.toString(); |
|
596 } |
|
597 |
|
598 /** |
|
599 * Tests the ServerHello for the presence (or not) of the status_request |
|
600 * or status_request_v2 hello extension. It is assumed that the provided |
|
601 * ByteBuffer has its position set at the first byte of the TLS record |
|
602 * containing the ServerHello and contains the entire hello message. Upon |
|
603 * successful completion of this method the ByteBuffer will have its |
|
604 * position reset to the initial offset in the buffer. If an exception is |
|
605 * thrown the position at the time of the exception will be preserved. |
|
606 * |
|
607 * @param statReqPresent true if the status_request hello extension should |
|
608 * be present. |
|
609 * @param statReqV2Present true if the status_request_v2 hello extension |
|
610 * should be present. |
|
611 * |
|
612 * @return true if the ServerHello's extension set matches the presence |
|
613 * booleans for status_request and status_request_v2. False if |
|
614 * not, or if the TLS record or message is of the wrong type. |
|
615 */ |
|
616 private static boolean checkServerHello(ByteBuffer data, |
|
617 boolean statReqPresent, boolean statReqV2Present) { |
|
618 boolean hasV1 = false; |
|
619 boolean hasV2 = false; |
|
620 Objects.requireNonNull(data); |
|
621 int startPos = data.position(); |
|
622 data.mark(); |
|
623 |
|
624 // Process the TLS record header |
|
625 int type = Byte.toUnsignedInt(data.get()); |
|
626 int ver_major = Byte.toUnsignedInt(data.get()); |
|
627 int ver_minor = Byte.toUnsignedInt(data.get()); |
|
628 int recLen = Short.toUnsignedInt(data.getShort()); |
|
629 |
|
630 // Simple sanity checks |
|
631 if (type != 22) { |
|
632 log("Not a handshake: Type = " + type); |
|
633 return false; |
|
634 } else if (recLen > data.remaining()) { |
|
635 log("Incomplete record in buffer: Record length = " + recLen + |
|
636 ", Remaining = " + data.remaining()); |
|
637 return false; |
|
638 } |
|
639 |
|
640 // Grab the handshake message header. |
|
641 int msgHdr = data.getInt(); |
|
642 int msgType = (msgHdr >> 24) & 0x000000FF; |
|
643 int msgLen = msgHdr & 0x00FFFFFF; |
|
644 |
|
645 // More simple sanity checks |
|
646 if (msgType != 2) { |
|
647 log("Not a ServerHello: Type = " + msgType); |
|
648 return false; |
|
649 } |
|
650 |
|
651 // Skip over the protocol version and server random |
|
652 data.position(data.position() + 34); |
|
653 |
|
654 // Jump past the session ID |
|
655 int sessLen = Byte.toUnsignedInt(data.get()); |
|
656 if (sessLen != 0) { |
|
657 data.position(data.position() + sessLen); |
|
658 } |
|
659 |
|
660 // Skip the cipher suite and compression method |
|
661 data.position(data.position() + 3); |
|
662 |
|
663 // Go through the extensions and look for the request extension |
|
664 // expected by the caller. |
|
665 int extsLen = Short.toUnsignedInt(data.getShort()); |
|
666 while (data.position() < recLen + startPos + 5) { |
|
667 int extType = Short.toUnsignedInt(data.getShort()); |
|
668 int extLen = Short.toUnsignedInt(data.getShort()); |
|
669 hasV1 |= (extType == ExtensionType.EXT_STATUS_REQUEST.id); |
|
670 hasV2 |= (extType == ExtensionType.EXT_STATUS_REQUEST_V2.id); |
|
671 data.position(data.position() + extLen); |
|
672 } |
|
673 |
|
674 if (hasV1 != statReqPresent) { |
|
675 log("The status_request extension is " + |
|
676 "inconsistent with the expected result: expected = " + |
|
677 statReqPresent + ", actual = " + hasV1); |
|
678 } |
|
679 if (hasV2 != statReqV2Present) { |
|
680 log("The status_request_v2 extension is " + |
|
681 "inconsistent with the expected result: expected = " + |
|
682 statReqV2Present + ", actual = " + hasV2); |
|
683 } |
|
684 |
|
685 // Reset the position to the initial spot at the start of this method. |
|
686 data.reset(); |
|
687 |
|
688 return ((hasV1 == statReqPresent) && (hasV2 == statReqV2Present)); |
|
689 } |
|
690 |
|
691 /** |
|
692 * Creates the PKI components necessary for this test, including |
|
693 * Root CA, Intermediate CA and SSL server certificates, the keystores |
|
694 * for each entity, a client trust store, and starts the OCSP responders. |
|
695 */ |
|
696 private static void createPKI() throws Exception { |
|
697 CertificateBuilder cbld = new CertificateBuilder(); |
|
698 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); |
|
699 keyGen.initialize(2048); |
|
700 KeyStore.Builder keyStoreBuilder = |
|
701 KeyStore.Builder.newInstance("PKCS12", null, |
|
702 new KeyStore.PasswordProtection(passwd.toCharArray())); |
|
703 |
|
704 // Generate Root, IntCA, EE keys |
|
705 KeyPair rootCaKP = keyGen.genKeyPair(); |
|
706 log("Generated Root CA KeyPair"); |
|
707 KeyPair intCaKP = keyGen.genKeyPair(); |
|
708 log("Generated Intermediate CA KeyPair"); |
|
709 KeyPair sslKP = keyGen.genKeyPair(); |
|
710 log("Generated SSL Cert KeyPair"); |
|
711 |
|
712 // Set up the Root CA Cert |
|
713 cbld.setSubjectName("CN=Root CA Cert, O=SomeCompany"); |
|
714 cbld.setPublicKey(rootCaKP.getPublic()); |
|
715 cbld.setSerialNumber(new BigInteger("1")); |
|
716 // Make a 3 year validity starting from 60 days ago |
|
717 long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60); |
|
718 long end = start + TimeUnit.DAYS.toMillis(1085); |
|
719 cbld.setValidity(new Date(start), new Date(end)); |
|
720 addCommonExts(cbld, rootCaKP.getPublic(), rootCaKP.getPublic()); |
|
721 addCommonCAExts(cbld); |
|
722 // Make our Root CA Cert! |
|
723 X509Certificate rootCert = cbld.build(null, rootCaKP.getPrivate(), |
|
724 "SHA256withRSA"); |
|
725 log("Root CA Created:\n" + certInfo(rootCert)); |
|
726 |
|
727 // Now build a keystore and add the keys and cert |
|
728 rootKeystore = keyStoreBuilder.getKeyStore(); |
|
729 java.security.cert.Certificate[] rootChain = {rootCert}; |
|
730 rootKeystore.setKeyEntry(ROOT_ALIAS, rootCaKP.getPrivate(), |
|
731 passwd.toCharArray(), rootChain); |
|
732 |
|
733 // Now fire up the OCSP responder |
|
734 rootOcsp = new SimpleOCSPServer(rootKeystore, passwd, ROOT_ALIAS, null); |
|
735 rootOcsp.enableLog(debug); |
|
736 rootOcsp.setNextUpdateInterval(3600); |
|
737 rootOcsp.start(); |
|
738 |
|
739 // Wait 5 seconds for server ready |
|
740 for (int i = 0; (i < 100 && !rootOcsp.isServerReady()); i++) { |
|
741 Thread.sleep(50); |
|
742 } |
|
743 if (!rootOcsp.isServerReady()) { |
|
744 throw new RuntimeException("Server not ready yet"); |
|
745 } |
|
746 |
|
747 rootOcspPort = rootOcsp.getPort(); |
|
748 String rootRespURI = "http://localhost:" + rootOcspPort; |
|
749 log("Root OCSP Responder URI is " + rootRespURI); |
|
750 |
|
751 // Now that we have the root keystore and OCSP responder we can |
|
752 // create our intermediate CA. |
|
753 cbld.reset(); |
|
754 cbld.setSubjectName("CN=Intermediate CA Cert, O=SomeCompany"); |
|
755 cbld.setPublicKey(intCaKP.getPublic()); |
|
756 cbld.setSerialNumber(new BigInteger("100")); |
|
757 // Make a 2 year validity starting from 30 days ago |
|
758 start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30); |
|
759 end = start + TimeUnit.DAYS.toMillis(730); |
|
760 cbld.setValidity(new Date(start), new Date(end)); |
|
761 addCommonExts(cbld, intCaKP.getPublic(), rootCaKP.getPublic()); |
|
762 addCommonCAExts(cbld); |
|
763 cbld.addAIAExt(Collections.singletonList(rootRespURI)); |
|
764 // Make our Intermediate CA Cert! |
|
765 X509Certificate intCaCert = cbld.build(rootCert, rootCaKP.getPrivate(), |
|
766 "SHA256withRSA"); |
|
767 log("Intermediate CA Created:\n" + certInfo(intCaCert)); |
|
768 |
|
769 // Provide intermediate CA cert revocation info to the Root CA |
|
770 // OCSP responder. |
|
771 Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo = |
|
772 new HashMap<>(); |
|
773 revInfo.put(intCaCert.getSerialNumber(), |
|
774 new SimpleOCSPServer.CertStatusInfo( |
|
775 SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD)); |
|
776 rootOcsp.updateStatusDb(revInfo); |
|
777 |
|
778 // Now build a keystore and add the keys, chain and root cert as a TA |
|
779 intKeystore = keyStoreBuilder.getKeyStore(); |
|
780 java.security.cert.Certificate[] intChain = {intCaCert, rootCert}; |
|
781 intKeystore.setKeyEntry(INT_ALIAS, intCaKP.getPrivate(), |
|
782 passwd.toCharArray(), intChain); |
|
783 intKeystore.setCertificateEntry(ROOT_ALIAS, rootCert); |
|
784 |
|
785 // Now fire up the Intermediate CA OCSP responder |
|
786 intOcsp = new SimpleOCSPServer(intKeystore, passwd, |
|
787 INT_ALIAS, null); |
|
788 intOcsp.enableLog(debug); |
|
789 intOcsp.setNextUpdateInterval(3600); |
|
790 intOcsp.start(); |
|
791 |
|
792 // Wait 5 seconds for server ready |
|
793 for (int i = 0; (i < 100 && !intOcsp.isServerReady()); i++) { |
|
794 Thread.sleep(50); |
|
795 } |
|
796 if (!intOcsp.isServerReady()) { |
|
797 throw new RuntimeException("Server not ready yet"); |
|
798 } |
|
799 |
|
800 intOcspPort = intOcsp.getPort(); |
|
801 String intCaRespURI = "http://localhost:" + intOcspPort; |
|
802 log("Intermediate CA OCSP Responder URI is " + intCaRespURI); |
|
803 |
|
804 // Last but not least, let's make our SSLCert and add it to its own |
|
805 // Keystore |
|
806 cbld.reset(); |
|
807 cbld.setSubjectName("CN=SSLCertificate, O=SomeCompany"); |
|
808 cbld.setPublicKey(sslKP.getPublic()); |
|
809 cbld.setSerialNumber(new BigInteger("4096")); |
|
810 // Make a 1 year validity starting from 7 days ago |
|
811 start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7); |
|
812 end = start + TimeUnit.DAYS.toMillis(365); |
|
813 cbld.setValidity(new Date(start), new Date(end)); |
|
814 |
|
815 // Add extensions |
|
816 addCommonExts(cbld, sslKP.getPublic(), intCaKP.getPublic()); |
|
817 boolean[] kuBits = {true, false, true, false, false, false, |
|
818 false, false, false}; |
|
819 cbld.addKeyUsageExt(kuBits); |
|
820 List<String> ekuOids = new ArrayList<>(); |
|
821 ekuOids.add("1.3.6.1.5.5.7.3.1"); |
|
822 ekuOids.add("1.3.6.1.5.5.7.3.2"); |
|
823 cbld.addExtendedKeyUsageExt(ekuOids); |
|
824 cbld.addSubjectAltNameDNSExt(Collections.singletonList("localhost")); |
|
825 cbld.addAIAExt(Collections.singletonList(intCaRespURI)); |
|
826 // Make our SSL Server Cert! |
|
827 X509Certificate sslCert = cbld.build(intCaCert, intCaKP.getPrivate(), |
|
828 "SHA256withRSA"); |
|
829 log("SSL Certificate Created:\n" + certInfo(sslCert)); |
|
830 |
|
831 // Provide SSL server cert revocation info to the Intermeidate CA |
|
832 // OCSP responder. |
|
833 revInfo = new HashMap<>(); |
|
834 revInfo.put(sslCert.getSerialNumber(), |
|
835 new SimpleOCSPServer.CertStatusInfo( |
|
836 SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD)); |
|
837 intOcsp.updateStatusDb(revInfo); |
|
838 |
|
839 // Now build a keystore and add the keys, chain and root cert as a TA |
|
840 serverKeystore = keyStoreBuilder.getKeyStore(); |
|
841 java.security.cert.Certificate[] sslChain = {sslCert, intCaCert, rootCert}; |
|
842 serverKeystore.setKeyEntry(SSL_ALIAS, sslKP.getPrivate(), |
|
843 passwd.toCharArray(), sslChain); |
|
844 serverKeystore.setCertificateEntry(ROOT_ALIAS, rootCert); |
|
845 |
|
846 // And finally a Trust Store for the client |
|
847 trustStore = keyStoreBuilder.getKeyStore(); |
|
848 trustStore.setCertificateEntry(ROOT_ALIAS, rootCert); |
|
849 } |
|
850 |
|
851 private static void addCommonExts(CertificateBuilder cbld, |
|
852 PublicKey subjKey, PublicKey authKey) throws IOException { |
|
853 cbld.addSubjectKeyIdExt(subjKey); |
|
854 cbld.addAuthorityKeyIdExt(authKey); |
|
855 } |
|
856 |
|
857 private static void addCommonCAExts(CertificateBuilder cbld) |
|
858 throws IOException { |
|
859 cbld.addBasicConstraintsExt(true, true, -1); |
|
860 // Set key usage bits for digitalSignature, keyCertSign and cRLSign |
|
861 boolean[] kuBitSettings = {true, false, false, false, false, true, |
|
862 true, false, false}; |
|
863 cbld.addKeyUsageExt(kuBitSettings); |
|
864 } |
|
865 |
|
866 /** |
|
867 * Helper routine that dumps only a few cert fields rather than |
|
868 * the whole toString() output. |
|
869 * |
|
870 * @param cert an X509Certificate to be displayed |
|
871 * |
|
872 * @return the String output of the issuer, subject and |
|
873 * serial number |
|
874 */ |
|
875 private static String certInfo(X509Certificate cert) { |
|
876 StringBuilder sb = new StringBuilder(); |
|
877 sb.append("Issuer: ").append(cert.getIssuerX500Principal()). |
|
878 append("\n"); |
|
879 sb.append("Subject: ").append(cert.getSubjectX500Principal()). |
|
880 append("\n"); |
|
881 sb.append("Serial: ").append(cert.getSerialNumber()).append("\n"); |
|
882 return sb.toString(); |
|
883 } |
|
884 |
|
885 private static class TestCase { |
|
886 public final String testName; |
|
887 public final ByteBuffer data; |
|
888 public final boolean statReqEnabled; |
|
889 public final boolean statReqV2Enabled; |
|
890 |
|
891 TestCase(String name, ByteBuffer buffer, boolean srEn, boolean srv2En) { |
|
892 testName = (name != null) ? name : ""; |
|
893 data = Objects.requireNonNull(buffer, |
|
894 "TestCase requires a non-null ByteBuffer"); |
|
895 statReqEnabled = srEn; |
|
896 statReqV2Enabled = srv2En; |
|
897 } |
|
898 } |
|
899 } |
|