Path: blob/master/test/jdk/javax/net/ssl/Stapling/SSLEngineWithStapling.java
41152 views
/*1* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223// SunJSSE does not support dynamic system properties, no way to re-use24// system properties in samevm/agentvm mode.2526/*27* @test28* @bug 8046321 815382929* @summary OCSP Stapling for TLS30* @library ../../../../java/security/testlibrary31* @build CertificateBuilder SimpleOCSPServer32* @run main/othervm SSLEngineWithStapling33*/3435/**36* A SSLEngine usage example which simplifies the presentation37* by removing the I/O and multi-threading concerns.38*39* The test creates two SSLEngines, simulating a client and server.40* The "transport" layer consists two byte buffers: think of them41* as directly connected pipes.42*43* Note, this is a *very* simple example: real code will be much more44* involved. For example, different threading and I/O models could be45* used, transport mechanisms could close unexpectedly, and so on.46*47* When this application runs, notice that several messages48* (wrap/unwrap) pass before any application data is consumed or49* produced. (For more information, please see the SSL/TLS50* specifications.) There may several steps for a successful handshake,51* so it's typical to see the following series of operations:52*53* client server message54* ====== ====== =======55* wrap() ... ClientHello56* ... unwrap() ClientHello57* ... wrap() ServerHello/Certificate58* unwrap() ... ServerHello/Certificate59* wrap() ... ClientKeyExchange60* wrap() ... ChangeCipherSpec61* wrap() ... Finished62* ... unwrap() ClientKeyExchange63* ... unwrap() ChangeCipherSpec64* ... unwrap() Finished65* ... wrap() ChangeCipherSpec66* ... wrap() Finished67* unwrap() ... ChangeCipherSpec68* unwrap() ... Finished69*/7071import javax.net.ssl.*;72import javax.net.ssl.SSLEngineResult.*;73import java.io.*;74import java.math.BigInteger;75import java.security.*;76import java.nio.*;77import java.security.cert.CertPathValidatorException;78import java.security.cert.PKIXBuilderParameters;79import java.security.cert.X509Certificate;80import java.security.cert.X509CertSelector;81import java.util.ArrayList;82import java.util.Collections;83import java.util.Date;84import java.util.HashMap;85import java.util.List;86import java.util.Map;87import java.util.concurrent.TimeUnit;8889import sun.security.testlibrary.SimpleOCSPServer;90import sun.security.testlibrary.CertificateBuilder;9192public class SSLEngineWithStapling {9394/*95* Enables logging of the SSLEngine operations.96*/97private static final boolean logging = true;9899/*100* Enables the JSSE system debugging system property:101*102* -Djavax.net.debug=all103*104* This gives a lot of low-level information about operations underway,105* including specific handshake messages, and might be best examined106* after gaining some familiarity with this application.107*/108private static final boolean debug = true;109110private SSLEngine clientEngine; // client Engine111private ByteBuffer clientOut; // write side of clientEngine112private ByteBuffer clientIn; // read side of clientEngine113114private SSLEngine serverEngine; // server Engine115private ByteBuffer serverOut; // write side of serverEngine116private ByteBuffer serverIn; // read side of serverEngine117118/*119* For data transport, this example uses local ByteBuffers. This120* isn't really useful, but the purpose of this example is to show121* SSLEngine concepts, not how to do network transport.122*/123private ByteBuffer cTOs; // "reliable" transport client->server124private ByteBuffer sTOc; // "reliable" transport server->client125126/*127* The following is to set up the keystores.128*/129static final String passwd = "passphrase";130static final String ROOT_ALIAS = "root";131static final String INT_ALIAS = "intermediate";132static final String SSL_ALIAS = "ssl";133134// PKI components we will need for this test135static KeyStore rootKeystore; // Root CA Keystore136static KeyStore intKeystore; // Intermediate CA Keystore137static KeyStore serverKeystore; // SSL Server Keystore138static KeyStore trustStore; // SSL Client trust store139static SimpleOCSPServer rootOcsp; // Root CA OCSP Responder140static int rootOcspPort; // Port number for root OCSP141static SimpleOCSPServer intOcsp; // Intermediate CA OCSP Responder142static int intOcspPort; // Port number for intermed. OCSP143144// Extra configuration parameters and constants145static final String[] TLS13ONLY = new String[] { "TLSv1.3" };146static final String[] TLS12MAX =147new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" };148149/*150* Main entry point for this test.151*/152public static void main(String args[]) throws Exception {153if (debug) {154System.setProperty("javax.net.debug", "ssl:handshake");155}156157// Create the PKI we will use for the test and start the OCSP servers158createPKI();159160// Set the certificate entry in the intermediate OCSP responder161// with a revocation date of 8 hours ago.162X509Certificate sslCert =163(X509Certificate)serverKeystore.getCertificate(SSL_ALIAS);164Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =165new HashMap<>();166revInfo.put(sslCert.getSerialNumber(),167new SimpleOCSPServer.CertStatusInfo(168SimpleOCSPServer.CertStatus.CERT_STATUS_REVOKED,169new Date(System.currentTimeMillis() -170TimeUnit.HOURS.toMillis(8))));171intOcsp.updateStatusDb(revInfo);172173// Create a list of TLS protocol configurations we can use to174// drive tests with different handshaking models.175List<String[]> allowedProtList = List.of(TLS12MAX, TLS13ONLY);176177for (String[] protocols : allowedProtList) {178SSLEngineWithStapling test = new SSLEngineWithStapling();179try {180test.runTest(protocols);181throw new RuntimeException("Expected failure due to " +182"revocation did not occur");183} catch (Exception e) {184if (!checkClientValidationFailure(e,185CertPathValidatorException.BasicReason.REVOKED)) {186System.out.println(187"*** Didn't find the exception we wanted");188throw e;189}190}191}192193System.out.println("Test Passed.");194}195196/*197* Create an initialized SSLContext to use for these tests.198*/199public SSLEngineWithStapling() throws Exception {200System.setProperty("javax.net.ssl.keyStore", "");201System.setProperty("javax.net.ssl.keyStorePassword", "");202System.setProperty("javax.net.ssl.trustStore", "");203System.setProperty("javax.net.ssl.trustStorePassword", "");204205// Enable OCSP Stapling on both client and server sides, but turn off206// client-side OCSP for revocation checking. This ensures that the207// revocation information from the test has to come via stapling.208System.setProperty("jdk.tls.client.enableStatusRequestExtension",209Boolean.toString(true));210System.setProperty("jdk.tls.server.enableStatusRequestExtension",211Boolean.toString(true));212Security.setProperty("ocsp.enable", "false");213}214215/*216* Run the test.217*218* Sit in a tight loop, both engines calling wrap/unwrap regardless219* of whether data is available or not. We do this until both engines220* report back they are closed.221*222* The main loop handles all of the I/O phases of the SSLEngine's223* lifetime:224*225* initial handshaking226* application data transfer227* engine closing228*229* One could easily separate these phases into separate230* sections of code.231*/232private void runTest(String[] protocols) throws Exception {233boolean dataDone = false;234235createSSLEngines(protocols);236createBuffers();237238SSLEngineResult clientResult; // results from client's last operation239SSLEngineResult serverResult; // results from server's last operation240241/*242* Examining the SSLEngineResults could be much more involved,243* and may alter the overall flow of the application.244*245* For example, if we received a BUFFER_OVERFLOW when trying246* to write to the output pipe, we could reallocate a larger247* pipe, but instead we wait for the peer to drain it.248*/249while (!isEngineClosed(clientEngine) ||250!isEngineClosed(serverEngine)) {251252log("================");253254clientResult = clientEngine.wrap(clientOut, cTOs);255log("client wrap: ", clientResult);256runDelegatedTasks(clientResult, clientEngine);257258serverResult = serverEngine.wrap(serverOut, sTOc);259log("server wrap: ", serverResult);260runDelegatedTasks(serverResult, serverEngine);261262cTOs.flip();263sTOc.flip();264265log("----");266267clientResult = clientEngine.unwrap(sTOc, clientIn);268log("client unwrap: ", clientResult);269runDelegatedTasks(clientResult, clientEngine);270271serverResult = serverEngine.unwrap(cTOs, serverIn);272log("server unwrap: ", serverResult);273runDelegatedTasks(serverResult, serverEngine);274275cTOs.compact();276sTOc.compact();277278/*279* After we've transfered all application data between the client280* and server, we close the clientEngine's outbound stream.281* This generates a close_notify handshake message, which the282* server engine receives and responds by closing itself.283*/284if (!dataDone && (clientOut.limit() == serverIn.position()) &&285(serverOut.limit() == clientIn.position())) {286287/*288* A sanity check to ensure we got what was sent.289*/290checkTransfer(serverOut, clientIn);291checkTransfer(clientOut, serverIn);292293log("\tClosing clientEngine's *OUTBOUND*...");294clientEngine.closeOutbound();295dataDone = true;296}297}298}299300/*301* Using the SSLContext created during object creation,302* create/configure the SSLEngines we'll use for this test.303*/304private void createSSLEngines(String[] protocols) throws Exception {305// Initialize the KeyManager and TrustManager for the server306KeyManagerFactory servKmf = KeyManagerFactory.getInstance("PKIX");307servKmf.init(serverKeystore, passwd.toCharArray());308TrustManagerFactory servTmf =309TrustManagerFactory.getInstance("PKIX");310servTmf.init(trustStore);311312// Initialize the TrustManager for the client with revocation checking313PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustStore,314new X509CertSelector());315pkixParams.setRevocationEnabled(true);316ManagerFactoryParameters mfp =317new CertPathTrustManagerParameters(pkixParams);318TrustManagerFactory cliTmf =319TrustManagerFactory.getInstance("PKIX");320cliTmf.init(mfp);321322// Create the SSLContexts from the factories323SSLContext servCtx = SSLContext.getInstance("TLS");324servCtx.init(servKmf.getKeyManagers(), servTmf.getTrustManagers(),325null);326SSLContext cliCtx = SSLContext.getInstance("TLS");327cliCtx.init(null, cliTmf.getTrustManagers(), null);328329330/*331* Configure the serverEngine to act as a server in the SSL/TLS332* handshake.333*/334serverEngine = servCtx.createSSLEngine();335serverEngine.setEnabledProtocols(protocols);336serverEngine.setUseClientMode(false);337serverEngine.setNeedClientAuth(false);338339/*340* Similar to above, but using client mode instead.341*/342clientEngine = cliCtx.createSSLEngine("client", 80);343clientEngine.setEnabledProtocols(protocols);344clientEngine.setUseClientMode(true);345}346347/*348* Create and size the buffers appropriately.349*/350private void createBuffers() {351352/*353* We'll assume the buffer sizes are the same354* between client and server.355*/356SSLSession session = clientEngine.getSession();357int appBufferMax = session.getApplicationBufferSize();358int netBufferMax = session.getPacketBufferSize();359360/*361* We'll make the input buffers a bit bigger than the max needed362* size, so that unwrap()s following a successful data transfer363* won't generate BUFFER_OVERFLOWS.364*365* We'll use a mix of direct and indirect ByteBuffers for366* tutorial purposes only. In reality, only use direct367* ByteBuffers when they give a clear performance enhancement.368*/369clientIn = ByteBuffer.allocate(appBufferMax + 50);370serverIn = ByteBuffer.allocate(appBufferMax + 50);371372cTOs = ByteBuffer.allocateDirect(netBufferMax);373sTOc = ByteBuffer.allocateDirect(netBufferMax);374375clientOut = ByteBuffer.wrap("Hi Server, I'm Client".getBytes());376serverOut = ByteBuffer.wrap("Hello Client, I'm Server".getBytes());377}378379/*380* If the result indicates that we have outstanding tasks to do,381* go ahead and run them in this thread.382*/383private static void runDelegatedTasks(SSLEngineResult result,384SSLEngine engine) throws Exception {385386if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {387Runnable runnable;388while ((runnable = engine.getDelegatedTask()) != null) {389log("\trunning delegated task...");390runnable.run();391}392HandshakeStatus hsStatus = engine.getHandshakeStatus();393if (hsStatus == HandshakeStatus.NEED_TASK) {394throw new Exception(395"handshake shouldn't need additional tasks");396}397log("\tnew HandshakeStatus: " + hsStatus);398}399}400401private static boolean isEngineClosed(SSLEngine engine) {402return (engine.isOutboundDone() && engine.isInboundDone());403}404405/*406* Simple check to make sure everything came across as expected.407*/408private static void checkTransfer(ByteBuffer a, ByteBuffer b)409throws Exception {410a.flip();411b.flip();412413if (!a.equals(b)) {414throw new Exception("Data didn't transfer cleanly");415} else {416log("\tData transferred cleanly");417}418419a.position(a.limit());420b.position(b.limit());421a.limit(a.capacity());422b.limit(b.capacity());423}424425/*426* Logging code427*/428private static boolean resultOnce = true;429430private static void log(String str, SSLEngineResult result) {431if (!logging) {432return;433}434if (resultOnce) {435resultOnce = false;436System.out.println("The format of the SSLEngineResult is: \n" +437"\t\"getStatus() / getHandshakeStatus()\" +\n" +438"\t\"bytesConsumed() / bytesProduced()\"\n");439}440HandshakeStatus hsStatus = result.getHandshakeStatus();441log(str +442result.getStatus() + "/" + hsStatus + ", " +443result.bytesConsumed() + "/" + result.bytesProduced() +444" bytes");445if (hsStatus == HandshakeStatus.FINISHED) {446log("\t...ready for application data");447}448}449450private static void log(String str) {451if (logging) {452System.out.println(str);453}454}455456/**457* Creates the PKI components necessary for this test, including458* Root CA, Intermediate CA and SSL server certificates, the keystores459* for each entity, a client trust store, and starts the OCSP responders.460*/461private static void createPKI() throws Exception {462CertificateBuilder cbld = new CertificateBuilder();463KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");464keyGen.initialize(2048);465KeyStore.Builder keyStoreBuilder =466KeyStore.Builder.newInstance("PKCS12", null,467new KeyStore.PasswordProtection(passwd.toCharArray()));468469// Generate Root, IntCA, EE keys470KeyPair rootCaKP = keyGen.genKeyPair();471log("Generated Root CA KeyPair");472KeyPair intCaKP = keyGen.genKeyPair();473log("Generated Intermediate CA KeyPair");474KeyPair sslKP = keyGen.genKeyPair();475log("Generated SSL Cert KeyPair");476477// Set up the Root CA Cert478cbld.setSubjectName("CN=Root CA Cert, O=SomeCompany");479cbld.setPublicKey(rootCaKP.getPublic());480cbld.setSerialNumber(new BigInteger("1"));481// Make a 3 year validity starting from 60 days ago482long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);483long end = start + TimeUnit.DAYS.toMillis(1085);484cbld.setValidity(new Date(start), new Date(end));485addCommonExts(cbld, rootCaKP.getPublic(), rootCaKP.getPublic());486addCommonCAExts(cbld);487// Make our Root CA Cert!488X509Certificate rootCert = cbld.build(null, rootCaKP.getPrivate(),489"SHA256withRSA");490log("Root CA Created:\n" + certInfo(rootCert));491492// Now build a keystore and add the keys and cert493rootKeystore = keyStoreBuilder.getKeyStore();494java.security.cert.Certificate[] rootChain = {rootCert};495rootKeystore.setKeyEntry(ROOT_ALIAS, rootCaKP.getPrivate(),496passwd.toCharArray(), rootChain);497498// Now fire up the OCSP responder499rootOcsp = new SimpleOCSPServer(rootKeystore, passwd, ROOT_ALIAS, null);500rootOcsp.enableLog(logging);501rootOcsp.setNextUpdateInterval(3600);502rootOcsp.start();503504// Wait 5 seconds for server ready505for (int i = 0; (i < 100 && !rootOcsp.isServerReady()); i++) {506Thread.sleep(50);507}508if (!rootOcsp.isServerReady()) {509throw new RuntimeException("Server not ready yet");510}511512rootOcspPort = rootOcsp.getPort();513String rootRespURI = "http://localhost:" + rootOcspPort;514log("Root OCSP Responder URI is " + rootRespURI);515516// Now that we have the root keystore and OCSP responder we can517// create our intermediate CA.518cbld.reset();519cbld.setSubjectName("CN=Intermediate CA Cert, O=SomeCompany");520cbld.setPublicKey(intCaKP.getPublic());521cbld.setSerialNumber(new BigInteger("100"));522// Make a 2 year validity starting from 30 days ago523start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30);524end = start + TimeUnit.DAYS.toMillis(730);525cbld.setValidity(new Date(start), new Date(end));526addCommonExts(cbld, intCaKP.getPublic(), rootCaKP.getPublic());527addCommonCAExts(cbld);528cbld.addAIAExt(Collections.singletonList(rootRespURI));529// Make our Intermediate CA Cert!530X509Certificate intCaCert = cbld.build(rootCert, rootCaKP.getPrivate(),531"SHA256withRSA");532log("Intermediate CA Created:\n" + certInfo(intCaCert));533534// Provide intermediate CA cert revocation info to the Root CA535// OCSP responder.536Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =537new HashMap<>();538revInfo.put(intCaCert.getSerialNumber(),539new SimpleOCSPServer.CertStatusInfo(540SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));541rootOcsp.updateStatusDb(revInfo);542543// Now build a keystore and add the keys, chain and root cert as a TA544intKeystore = keyStoreBuilder.getKeyStore();545java.security.cert.Certificate[] intChain = {intCaCert, rootCert};546intKeystore.setKeyEntry(INT_ALIAS, intCaKP.getPrivate(),547passwd.toCharArray(), intChain);548intKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);549550// Now fire up the Intermediate CA OCSP responder551intOcsp = new SimpleOCSPServer(intKeystore, passwd,552INT_ALIAS, null);553intOcsp.enableLog(logging);554intOcsp.setNextUpdateInterval(3600);555intOcsp.start();556557// Wait 5 seconds for server ready558for (int i = 0; (i < 100 && !intOcsp.isServerReady()); i++) {559Thread.sleep(50);560}561if (!intOcsp.isServerReady()) {562throw new RuntimeException("Server not ready yet");563}564565intOcspPort = intOcsp.getPort();566String intCaRespURI = "http://localhost:" + intOcspPort;567log("Intermediate CA OCSP Responder URI is " + intCaRespURI);568569// Last but not least, let's make our SSLCert and add it to its own570// Keystore571cbld.reset();572cbld.setSubjectName("CN=SSLCertificate, O=SomeCompany");573cbld.setPublicKey(sslKP.getPublic());574cbld.setSerialNumber(new BigInteger("4096"));575// Make a 1 year validity starting from 7 days ago576start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);577end = start + TimeUnit.DAYS.toMillis(365);578cbld.setValidity(new Date(start), new Date(end));579580// Add extensions581addCommonExts(cbld, sslKP.getPublic(), intCaKP.getPublic());582boolean[] kuBits = {true, false, true, false, false, false,583false, false, false};584cbld.addKeyUsageExt(kuBits);585List<String> ekuOids = new ArrayList<>();586ekuOids.add("1.3.6.1.5.5.7.3.1");587ekuOids.add("1.3.6.1.5.5.7.3.2");588cbld.addExtendedKeyUsageExt(ekuOids);589cbld.addSubjectAltNameDNSExt(Collections.singletonList("localhost"));590cbld.addAIAExt(Collections.singletonList(intCaRespURI));591// Make our SSL Server Cert!592X509Certificate sslCert = cbld.build(intCaCert, intCaKP.getPrivate(),593"SHA256withRSA");594log("SSL Certificate Created:\n" + certInfo(sslCert));595596// Provide SSL server cert revocation info to the Intermeidate CA597// OCSP responder.598revInfo = new HashMap<>();599revInfo.put(sslCert.getSerialNumber(),600new SimpleOCSPServer.CertStatusInfo(601SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));602intOcsp.updateStatusDb(revInfo);603604// Now build a keystore and add the keys, chain and root cert as a TA605serverKeystore = keyStoreBuilder.getKeyStore();606java.security.cert.Certificate[] sslChain = {sslCert, intCaCert, rootCert};607serverKeystore.setKeyEntry(SSL_ALIAS, sslKP.getPrivate(),608passwd.toCharArray(), sslChain);609serverKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);610611// And finally a Trust Store for the client612trustStore = keyStoreBuilder.getKeyStore();613trustStore.setCertificateEntry(ROOT_ALIAS, rootCert);614}615616private static void addCommonExts(CertificateBuilder cbld,617PublicKey subjKey, PublicKey authKey) throws IOException {618cbld.addSubjectKeyIdExt(subjKey);619cbld.addAuthorityKeyIdExt(authKey);620}621622private static void addCommonCAExts(CertificateBuilder cbld)623throws IOException {624cbld.addBasicConstraintsExt(true, true, -1);625// Set key usage bits for digitalSignature, keyCertSign and cRLSign626boolean[] kuBitSettings = {true, false, false, false, false, true,627true, false, false};628cbld.addKeyUsageExt(kuBitSettings);629}630631/**632* Helper routine that dumps only a few cert fields rather than633* the whole toString() output.634*635* @param cert an X509Certificate to be displayed636*637* @return the String output of the issuer, subject and638* serial number639*/640private static String certInfo(X509Certificate cert) {641StringBuilder sb = new StringBuilder();642sb.append("Issuer: ").append(cert.getIssuerX500Principal()).643append("\n");644sb.append("Subject: ").append(cert.getSubjectX500Principal()).645append("\n");646sb.append("Serial: ").append(cert.getSerialNumber()).append("\n");647return sb.toString();648}649650/**651* Checks a validation failure to see if it failed for the reason we think652* it should. This comes in as an SSLException of some sort, but it653* encapsulates a CertPathValidatorException at some point in the654* exception stack.655*656* @param e the exception thrown at the top level657* @param reason the underlying CertPathValidatorException BasicReason658* we are expecting it to have.659*660* @return true if the reason matches up, false otherwise.661*/662static boolean checkClientValidationFailure(Exception e,663CertPathValidatorException.BasicReason reason) {664boolean result = false;665666// Locate the CertPathValidatorException. If one667// Does not exist, then it's an automatic failure of668// the test.669Throwable curExc = e;670CertPathValidatorException cpve = null;671while (curExc != null) {672if (curExc instanceof CertPathValidatorException) {673cpve = (CertPathValidatorException)curExc;674}675curExc = curExc.getCause();676}677678// If we get through the loop and cpve is null then we679// we didn't find CPVE and this is a failure680if (cpve != null) {681if (cpve.getReason() == reason) {682result = true;683} else {684System.out.println("CPVE Reason Mismatch: Expected = " +685reason + ", Actual = " + cpve.getReason());686}687} else {688System.out.println("Failed to find an expected CPVE");689}690691return result;692}693}694695696