/* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIMatcher; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManagerFactory; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Basic class to inherit SSLEngine test cases from it. Tests apply for * the TLS or DTLS security protocols and their versions. */ abstract public class SSLEngineTestCase { public enum Ciphers { /** * Ciphers supported by the tested SSLEngine without those with * kerberos authentication. */ SUPPORTED_NON_KRB_CIPHERS(SSLEngineTestCase.SUPPORTED_NON_KRB_CIPHERS, "Supported non kerberos"), /** * Ciphers supported by the tested SSLEngine without those with * kerberos authentication and without those with SHA256 ans SHA384. */ SUPPORTED_NON_KRB_NON_SHA_CIPHERS( SSLEngineTestCase.SUPPORTED_NON_KRB_NON_SHA_CIPHERS, "Supported non kerberos non SHA256 and SHA384"), /** * Ciphers supported by the tested SSLEngine with kerberos * authentication. */ SUPPORTED_KRB_CIPHERS(SSLEngineTestCase.SUPPORTED_KRB_CIPHERS, "Supported kerberos"), /** * Ciphers enabled by default for the tested SSLEngine without kerberos * and anon. */ ENABLED_NON_KRB_NOT_ANON_CIPHERS( SSLEngineTestCase.ENABLED_NON_KRB_NOT_ANON_CIPHERS, "Enabled by default non kerberos not anonymous"), /** * Ciphers unsupported by the tested SSLEngine. */ UNSUPPORTED_CIPHERS(SSLEngineTestCase.UNSUPPORTED_CIPHERS, "Unsupported"); Ciphers(String[] ciphers, String description) { this.ciphers = ciphers; this.description = description; } final String[] ciphers; final String description; } /** * Enumeration used to distinguish handshake mode in * {@link SSLEngineTestCase#doHandshake(javax.net.ssl.SSLEngine, * javax.net.ssl.SSLEngine, int, SSLEngineTestCase.HandshakeMode, boolean) * SSLEngineTestCase.doHandshake} method. */ public enum HandshakeMode { /** * Initial handshake done for the first time: both engines call * {@link SSLEngine#beginHandshake()} method. */ INITIAL_HANDSHAKE, /** * Repeated handshake done by client: client engine calls * {@link SSLEngine#beginHandshake()} method. */ REHANDSHAKE_BEGIN_CLIENT, /** * Repeated handshake done by server: server engine calls * {@link SSLEngine#beginHandshake()} method. */ REHANDSHAKE_BEGIN_SERVER; } /** * Security protocol to be tested: "TLS" or "DTLS" or their versions, * e.g. "TLSv1", "TLSv1.1", "TLSv1.2", "DTLSv1.0", "DTLSv1.2". */ public static final String TESTED_SECURITY_PROTOCOL = System.getProperty("test.security.protocol", "TLS"); /** * Test mode: "norm", "norm_sni" or "krb". * Modes "norm" and "norm_sni" are used to run * with all supported non-kerberos ciphers. * Mode "krb" is used to run with kerberos ciphers. */ public static final String TEST_MODE = System.getProperty("test.mode", "norm"); private static final String FS = System.getProperty("file.separator", "/"); private static final String PATH_TO_STORES = ".." + FS + "etc"; private static final String KEY_STORE_FILE = "keystore"; private static final String TRUST_STORE_FILE = "truststore"; private static final String PASSWD = "passphrase"; private static final String KEY_FILE_NAME = System.getProperty("test.src", ".") + FS + PATH_TO_STORES + FS + KEY_STORE_FILE; private static final String TRUST_FILE_NAME = System.getProperty("test.src", ".") + FS + PATH_TO_STORES + FS + TRUST_STORE_FILE; // Need an enhancement to use none-static mutable global variables. private static ByteBuffer net; private static boolean doUnwrapForNotHandshakingStatus; private static boolean endHandshakeLoop = false; private static final int MAX_HANDSHAKE_LOOPS = 100; private static final String EXCHANGE_MSG_SENT = "Hello, peer!"; private static final String TEST_SRC = System.getProperty("test.src", "."); private static final String KTAB_FILENAME = "krb5.keytab.data"; private static final String KRB_REALM = "TEST.REALM"; private static final String KRBTGT_PRINCIPAL = "krbtgt/" + KRB_REALM; private static final String KRB_USER = "USER"; private static final String KRB_USER_PASSWORD = "password"; private static final String KRB_USER_PRINCIPAL = KRB_USER + "@" + KRB_REALM; private static final String KRB5_CONF_FILENAME = "krb5.conf"; private static final String PATH_TO_COMMON = ".." + FS + "TLSCommon"; private static final String JAAS_CONF_FILE = PATH_TO_COMMON + FS + "jaas.conf"; private static final int DELAY = 1000; private static final String HOST = "localhost"; private static final String SERVER_NAME = "service.localhost"; private static final String SNI_PATTERN = ".*"; private static final String[] SUPPORTED_NON_KRB_CIPHERS; static { try { String[] allSupportedCiphers = getContext() .createSSLEngine().getSupportedCipherSuites(); List supportedCiphersList = new LinkedList<>(); for (String cipher : allSupportedCiphers) { if (!cipher.contains("KRB5") && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) { supportedCiphersList.add(cipher); } } SUPPORTED_NON_KRB_CIPHERS = supportedCiphersList.toArray(new String[0]); } catch (Exception ex) { throw new Error("Unexpected issue", ex); } } private static final String[] SUPPORTED_NON_KRB_NON_SHA_CIPHERS; static { try { String[] allSupportedCiphers = getContext() .createSSLEngine().getSupportedCipherSuites(); List supportedCiphersList = new LinkedList<>(); for (String cipher : allSupportedCiphers) { if (!cipher.contains("KRB5") && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV") && !cipher.endsWith("_SHA256") && !cipher.endsWith("_SHA384")) { supportedCiphersList.add(cipher); } } SUPPORTED_NON_KRB_NON_SHA_CIPHERS = supportedCiphersList.toArray(new String[0]); } catch (Exception ex) { throw new Error("Unexpected issue", ex); } } private static final String[] SUPPORTED_KRB_CIPHERS; static { try { String[] allSupportedCiphers = getContext() .createSSLEngine().getSupportedCipherSuites(); List supportedCiphersList = new LinkedList<>(); for (String cipher : allSupportedCiphers) { if (cipher.contains("KRB5") && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) { supportedCiphersList.add(cipher); } } SUPPORTED_KRB_CIPHERS = supportedCiphersList.toArray(new String[0]); } catch (Exception ex) { throw new Error("Unexpected issue", ex); } } private static final String[] ENABLED_NON_KRB_NOT_ANON_CIPHERS; static { try { SSLEngine temporary = getContext().createSSLEngine(); temporary.setUseClientMode(true); String[] enabledCiphers = temporary.getEnabledCipherSuites(); List enabledCiphersList = new LinkedList<>(); for (String cipher : enabledCiphers) { if (!cipher.contains("anon") && !cipher.contains("KRB5") && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) { enabledCiphersList.add(cipher); } } ENABLED_NON_KRB_NOT_ANON_CIPHERS = enabledCiphersList.toArray(new String[0]); } catch (Exception ex) { throw new Error("Unexpected issue", ex); } } private static final String[] UNSUPPORTED_CIPHERS = { "SSL_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA", "SSL_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA", "SSL_DHE_DSS_WITH_RC4_128_SHA", "SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", "SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA", "SSL_DH_DSS_WITH_DES_CBC_SHA", "SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA", "SSL_DH_RSA_WITH_DES_CBC_SHA", "SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA", "SSL_FORTEZZA_DMS_WITH_NULL_SHA", "SSL_RSA_EXPORT1024_WITH_DES_CBC_SHA", "SSL_RSA_EXPORT1024_WITH_RC4_56_SHA", "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA", "SSL_RSA_FIPS_WITH_DES_CBC_SHA", "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", "TLS_KRB5_WITH_IDEA_CBC_MD5", "TLS_KRB5_WITH_IDEA_CBC_SHA", "SSL_RSA_WITH_IDEA_CBC_SHA", "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", "TLS_DH_DSS_WITH_AES_256_GCM_SHA384" }; private final int maxPacketSize; /** * Constructs test case with the given MFLN maxMacketSize. * * @param maxPacketSize - MLFN extension max packet size. */ public SSLEngineTestCase(int maxPacketSize) { this.maxPacketSize = maxPacketSize; } /** * Constructs test case with {@code maxPacketSize = 0}. */ public SSLEngineTestCase() { this.maxPacketSize = 0; } /** * Wraps data with the specified engine. * * @param engine - SSLEngine that wraps data. * @param wrapper - Set wrapper id, e.g. "server" of "client". * Used for logging only. * @param maxPacketSize - Max packet size to check that MFLN extension * works or zero for no check. * @param app - Buffer with data to wrap. * @return - Buffer with wrapped data. * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doWrap(SSLEngine engine, String wrapper, int maxPacketSize, ByteBuffer app) throws SSLException { return doWrap(engine, wrapper, maxPacketSize, app, SSLEngineResult.Status.OK, null); } /** * Wraps data with the specified engine. * * @param engine - SSLEngine that wraps data. * @param wrapper - Set wrapper id, e.g. "server" of "client". * Used for logging only. * @param maxPacketSize - Max packet size to check that MFLN extension * works or zero for no check. * @param app - Buffer with data to wrap. * @param result - Array which first element will be used to * output wrap result object. * @return - Buffer with wrapped data. * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doWrap(SSLEngine engine, String wrapper, int maxPacketSize, ByteBuffer app, SSLEngineResult[] result) throws SSLException { return doWrap(engine, wrapper, maxPacketSize, app, SSLEngineResult.Status.OK, result); } /** * Wraps data with the specified engine. * * @param engine - SSLEngine that wraps data. * @param wrapper - Set wrapper id, e.g. "server" of "client". * Used for logging only. * @param maxPacketSize - Max packet size to check that MFLN extension * works or zero for no check. * @param app - Buffer with data to wrap. * @param wantedStatus - Specifies expected result status of wrapping. * @return - Buffer with wrapped data. * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doWrap(SSLEngine engine, String wrapper, int maxPacketSize, ByteBuffer app, SSLEngineResult.Status wantedStatus) throws SSLException { return doWrap(engine, wrapper, maxPacketSize, app, wantedStatus, null); } /** * Wraps data with the specified engine. * * @param engine - SSLEngine that wraps data. * @param wrapper - Set wrapper id, e.g. "server" of "client". * Used for logging only. * @param maxPacketSize - Max packet size to check that MFLN extension * works or zero for no check. * @param app - Buffer with data to wrap. * @param wantedStatus - Specifies expected result status of wrapping. * @param result - Array which first element will be used to output * wrap result object. * @return - Buffer with wrapped data. * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doWrap(SSLEngine engine, String wrapper, int maxPacketSize, ByteBuffer app, SSLEngineResult.Status wantedStatus, SSLEngineResult[] result) throws SSLException { ByteBuffer net = ByteBuffer.allocate(engine.getSession() .getPacketBufferSize()); SSLEngineResult r = engine.wrap(app, net); net.flip(); int length = net.remaining(); System.out.println(wrapper + " wrapped " + length + " bytes."); System.out.println(wrapper + " handshake status is " + engine.getHandshakeStatus()); if (maxPacketSize < length && maxPacketSize != 0) { throw new AssertionError("Handshake wrapped net buffer length " + length + " exceeds maximum packet size " + maxPacketSize); } checkResult(r, wantedStatus); if (result != null && result.length > 0) { result[0] = r; } return net; } /** * Unwraps data with the specified engine. * * @param engine - SSLEngine that unwraps data. * @param unwrapper - Set unwrapper id, e.g. "server" of "client". Used for * logging only. * @param net - Buffer with data to unwrap. * @return - Buffer with unwrapped data. * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper, ByteBuffer net) throws SSLException { return doUnWrap(engine, unwrapper, net, SSLEngineResult.Status.OK, null); } /** * Unwraps data with the specified engine. * * @param engine - SSLEngine that unwraps data. * @param unwrapper - Set unwrapper id, e.g. "server" of "client". Used for * logging only. * @param net - Buffer with data to unwrap. * @param result - Array which first element will be used to output wrap * result object. * @return - Buffer with unwrapped data. * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper, ByteBuffer net, SSLEngineResult[] result) throws SSLException { return doUnWrap(engine, unwrapper, net, SSLEngineResult.Status.OK, result); } /** * Unwraps data with the specified engine. * * @param engine - SSLEngine that unwraps data. * @param unwrapper - Set unwrapper id, e.g. "server" of "client". * Used for logging only. * @param net - Buffer with data to unwrap. * @param wantedStatus - Specifies expected result status of wrapping. * @return - Buffer with unwrapped data. * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper, ByteBuffer net, SSLEngineResult.Status wantedStatus) throws SSLException { return doUnWrap(engine, unwrapper, net, wantedStatus, null); } /** * Unwraps data with the specified engine. * * @param engine - SSLEngine that unwraps data. * @param unwrapper - Set unwrapper id, e.g. "server" of "client". * Used for logging only. * @param net - Buffer with data to unwrap. * @param wantedStatus - Specifies expected result status of wrapping. * @param result - Array which first element will be used to output * wrap result object. * @return - Buffer with unwrapped data. * @throws SSLException - thrown on engine errors. */ public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper, ByteBuffer net, SSLEngineResult.Status wantedStatus, SSLEngineResult[] result) throws SSLException { ByteBuffer app = ByteBuffer.allocate( engine.getSession().getApplicationBufferSize()); int length = net.remaining(); System.out.println(unwrapper + " unwrapping " + length + " bytes..."); SSLEngineResult r = engine.unwrap(net, app); app.flip(); System.out.println(unwrapper + " handshake status is " + engine.getHandshakeStatus()); checkResult(r, wantedStatus); if (result != null && result.length > 0) { result[0] = r; } return app; } /** * Does the handshake of the two specified engines according to the * {@code mode} specified. * * @param clientEngine - Client SSLEngine. * @param serverEngine - Server SSLEngine. * @param maxPacketSize - Maximum packet size for MFLN of zero for no limit. * @param mode - Handshake mode according to * {@link HandshakeMode} enum. * @throws SSLException - thrown on engine errors. */ public static void doHandshake(SSLEngine clientEngine, SSLEngine serverEngine, int maxPacketSize, HandshakeMode mode) throws SSLException { doHandshake(clientEngine, serverEngine, maxPacketSize, mode, false); } /** * Does the handshake of the two specified engines according to the * {@code mode} specified. * * @param clientEngine - Client SSLEngine. * @param serverEngine - Server SSLEngine. * @param maxPacketSize - Maximum packet size for MFLN of zero * for no limit. * @param mode - Handshake mode according to * {@link HandshakeMode} enum. * @param enableReplicatedPacks - Set {@code true} to enable replicated * packet sending. * @throws SSLException - thrown on engine errors. */ public static void doHandshake(SSLEngine clientEngine, SSLEngine serverEngine, int maxPacketSize, HandshakeMode mode, boolean enableReplicatedPacks) throws SSLException { System.out.println("============================================="); System.out.println("Starting handshake " + mode.name()); int loop = 0; if (maxPacketSize < 0) { throw new Error("Test issue: maxPacketSize is less than zero!"); } SSLParameters params = clientEngine.getSSLParameters(); params.setMaximumPacketSize(maxPacketSize); clientEngine.setSSLParameters(params); params = serverEngine.getSSLParameters(); params.setMaximumPacketSize(maxPacketSize); serverEngine.setSSLParameters(params); SSLEngine firstEngine; SSLEngine secondEngine; switch (mode) { case INITIAL_HANDSHAKE: firstEngine = clientEngine; secondEngine = serverEngine; doUnwrapForNotHandshakingStatus = false; clientEngine.beginHandshake(); serverEngine.beginHandshake(); break; case REHANDSHAKE_BEGIN_CLIENT: firstEngine = clientEngine; secondEngine = serverEngine; doUnwrapForNotHandshakingStatus = true; clientEngine.beginHandshake(); break; case REHANDSHAKE_BEGIN_SERVER: firstEngine = serverEngine; secondEngine = clientEngine; doUnwrapForNotHandshakingStatus = true; serverEngine.beginHandshake(); break; default: throw new Error("Test issue: unknown handshake mode"); } endHandshakeLoop = false; while (!endHandshakeLoop) { if (++loop > MAX_HANDSHAKE_LOOPS) { throw new Error("Too much loops for handshaking"); } System.out.println("============================================"); System.out.println("Handshake loop " + loop + ": round 1"); System.out.println("=========================="); handshakeProcess(firstEngine, secondEngine, maxPacketSize, enableReplicatedPacks); if (endHandshakeLoop) { break; } System.out.println("Handshake loop " + loop + ": round 2"); System.out.println("=========================="); handshakeProcess(secondEngine, firstEngine, maxPacketSize, enableReplicatedPacks); } } /** * Routine to send application data from one SSLEngine to another. * * @param fromEngine - Sending engine. * @param toEngine - Receiving engine. * @return - Result of unwrap method of the receiving engine. * @throws SSLException - thrown on engine errors. */ public static SSLEngineResult sendApplicationData(SSLEngine fromEngine, SSLEngine toEngine) throws SSLException { String sender = null; String reciever = null; String excMsgSent = EXCHANGE_MSG_SENT; if (fromEngine.getUseClientMode() && !toEngine.getUseClientMode()) { sender = "Client"; reciever = "Server"; excMsgSent += " Client."; } else if (toEngine.getUseClientMode() && !fromEngine.getUseClientMode()) { sender = "Server"; reciever = "Client"; excMsgSent += " Server."; } else { throw new Error("Test issue: both engines are in the same mode"); } System.out.println("============================================="); System.out.println("Trying to send application data from " + sender + " to " + reciever); ByteBuffer clientAppSent = ByteBuffer.wrap(excMsgSent.getBytes()); net = doWrap(fromEngine, sender, 0, clientAppSent); SSLEngineResult[] r = new SSLEngineResult[1]; ByteBuffer serverAppRecv = doUnWrap(toEngine, reciever, net, r); byte[] serverAppRecvTrunc = Arrays.copyOf(serverAppRecv.array(), serverAppRecv.limit()); String msgRecv = new String(serverAppRecvTrunc); if (!msgRecv.equals(excMsgSent)) { throw new AssertionError(sender + " to " + reciever + ": application data" + " has been altered while sending." + " Message sent: " + "\"" + excMsgSent + "\"." + " Message recieved: " + "\"" + msgRecv + "\"."); } System.out.println("Successful sending application data from " + sender + " to " + reciever); return r[0]; } /** * Close engines by sending "close outbound" message from one SSLEngine to * another. * * @param fromEngine - Sending engine. * @param toEngine - Receiving engine. * @throws SSLException - thrown on engine errors. */ public static void closeEngines(SSLEngine fromEngine, SSLEngine toEngine) throws SSLException { String from = null; String to = null; ByteBuffer app; if (fromEngine.getUseClientMode() && !toEngine.getUseClientMode()) { from = "Client"; to = "Server"; } else if (toEngine.getUseClientMode() && !fromEngine.getUseClientMode()) { from = "Server"; to = "Client"; } else { throw new Error("Both engines are in the same mode"); } System.out.println("============================================="); System.out.println( "Trying to close engines from " + from + " to " + to); // Sending close outbound request to peer fromEngine.closeOutbound(); app = ByteBuffer.allocate( fromEngine.getSession().getApplicationBufferSize()); net = doWrap(fromEngine, from, 0, app, SSLEngineResult.Status.CLOSED); doUnWrap(toEngine, to, net, SSLEngineResult.Status.CLOSED); app = ByteBuffer.allocate( fromEngine.getSession().getApplicationBufferSize()); net = doWrap(toEngine, to, 0, app, SSLEngineResult.Status.CLOSED); doUnWrap(fromEngine, from, net, SSLEngineResult.Status.CLOSED); if (!toEngine.isInboundDone()) { throw new AssertionError(from + " sent close request to " + to + ", but " + to + "did not close inbound."); } // Executing close inbound fromEngine.closeInbound(); app = ByteBuffer.allocate( fromEngine.getSession().getApplicationBufferSize()); net = doWrap(fromEngine, from, 0, app, SSLEngineResult.Status.CLOSED); doUnWrap(toEngine, to, net, SSLEngineResult.Status.CLOSED); if (!toEngine.isOutboundDone()) { throw new AssertionError(from + "sent close request to " + to + ", but " + to + "did not close outbound."); } System.out.println("Successful closing from " + from + " to " + to); } /** * Runs the same test case for all given {@code ciphers}. Method counts all * failures and throws {@code AssertionError} if one or more tests fail. * * @param ciphers - Ciphers that should be tested. */ public void runTests(Ciphers ciphers) { int total = ciphers.ciphers.length; int failed = testSomeCiphers(ciphers); if (failed > 0) { throw new AssertionError("" + failed + " of " + total + " tests failed!"); } System.out.println("All tests passed!"); } /** * Runs test cases for ciphers defined by the test mode. */ public void runTests() { switch (TEST_MODE) { case "norm": case "norm_sni": switch (TESTED_SECURITY_PROTOCOL) { case "DTLSv1.0": case "TLSv1": case "TLSv1.1": runTests(Ciphers.SUPPORTED_NON_KRB_NON_SHA_CIPHERS); break; default: runTests(Ciphers.SUPPORTED_NON_KRB_CIPHERS); } break; case "krb": runTests(Ciphers.SUPPORTED_KRB_CIPHERS); break; default: throw new Error( "Test error: unexpected test mode: " + TEST_MODE); } } /** * Returns maxPacketSize value used for MFLN extension testing * * @return - MLFN extension max packet size. */ public int getMaxPacketSize() { return maxPacketSize; } /** * Checks that status of result {@code r} is {@code wantedStatus}. * * @param r - Result. * @param wantedStatus - Wanted status of the result. * @throws AssertionError - if status or {@code r} is not * {@code wantedStatus}. */ public static void checkResult(SSLEngineResult r, SSLEngineResult.Status wantedStatus) { SSLEngineResult.Status rs = r.getStatus(); if (!rs.equals(wantedStatus)) { throw new AssertionError("Unexpected status " + rs.name() + ", should be " + wantedStatus.name()); } } /** * Returns SSLContext with TESTED_SECURITY_PROTOCOL protocol and * sets up keys. * * @return - SSLContext with a protocol specified by * TESTED_SECURITY_PROTOCOL. */ public static SSLContext getContext() { try { java.security.Security.setProperty( "jdk.tls.disabledAlgorithms", ""); java.security.Security.setProperty( "jdk.certpath.disabledAlgorithms", ""); KeyStore ks = KeyStore.getInstance("JKS"); KeyStore ts = KeyStore.getInstance("JKS"); char[] passphrase = PASSWD.toCharArray(); try (FileInputStream keyFileStream = new FileInputStream(KEY_FILE_NAME)) { ks.load(keyFileStream, passphrase); } try (FileInputStream trustFileStream = new FileInputStream(TRUST_FILE_NAME)) { ts.load(trustFileStream, passphrase); } KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, passphrase); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ts); SSLContext sslCtx = SSLContext.getInstance(TESTED_SECURITY_PROTOCOL); sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); return sslCtx; } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException ex) { throw new Error("Unexpected exception", ex); } } /** * Sets up and starts kerberos KDC server. */ public static void setUpAndStartKDC() { String servicePrincipal = "host/" + SERVER_NAME + "@" + KRB_REALM; Map principals = new HashMap<>(); principals.put(KRB_USER_PRINCIPAL, KRB_USER_PASSWORD); principals.put(KRBTGT_PRINCIPAL, null); principals.put(servicePrincipal, null); System.setProperty("java.security.krb5.conf", KRB5_CONF_FILENAME); startKDC(KRB_REALM, principals, KTAB_FILENAME); System.setProperty("java.security.auth.login.config", TEST_SRC + FS + JAAS_CONF_FILE); System.setProperty("javax.security.auth.useSubjectCredsOnly", "false"); } /** * Sets up and starts kerberos KDC server if * SSLEngineTestCase.TEST_MODE is "krb". */ public static void setUpAndStartKDCIfNeeded() { if (TEST_MODE.equals("krb")) { setUpAndStartKDC(); } } /** * Returns client ssl engine. * * @param context - SSLContext to get SSLEngine from. * @param useSNI - flag used to enable or disable using SNI extension. * Needed for Kerberos. */ public static SSLEngine getClientSSLEngine( SSLContext context, boolean useSNI) { SSLEngine clientEngine = context.createSSLEngine(HOST, 80); clientEngine.setUseClientMode(true); if (useSNI) { SNIHostName serverName = new SNIHostName(SERVER_NAME); List serverNames = new ArrayList<>(); serverNames.add(serverName); SSLParameters params = clientEngine.getSSLParameters(); params.setServerNames(serverNames); clientEngine.setSSLParameters(params); } return clientEngine; } /** * Returns server ssl engine. * * @param context - SSLContext to get SSLEngine from. * @param useSNI - flag used to enable or disable using SNI extension. * Needed for Kerberos. */ public static SSLEngine getServerSSLEngine( SSLContext context, boolean useSNI) { SSLEngine serverEngine = context.createSSLEngine(); serverEngine.setUseClientMode(false); if (useSNI) { SNIMatcher matcher = SNIHostName.createSNIMatcher(SNI_PATTERN); List matchers = new ArrayList<>(); matchers.add(matcher); SSLParameters params = serverEngine.getSSLParameters(); params.setSNIMatchers(matchers); serverEngine.setSSLParameters(params); } return serverEngine; } /** * Runs the test case for one cipher suite. * * @param cipher - Cipher suite name. * @throws SSLException - If tests fails. */ abstract protected void testOneCipher(String cipher) throws SSLException; /** * Iterates through an array of ciphers and runs the same test case for * every entry. * * @param ciphers - Array of cipher names. * @return - Number of tests failed. */ protected int testSomeCiphers(Ciphers ciphers) { int failedNum = 0; String description = ciphers.description; System.out.println("==============================================="); System.out.println(description + " ciphers testing"); System.out.println("==========================================="); for (String cs : ciphers.ciphers) { System.out.println("---------------------------------------"); System.out.println("Testing cipher suite " + cs); System.out.println("---------------------------------------"); Throwable error = null; // Reset global mutable static variables net = null; doUnwrapForNotHandshakingStatus = false; endHandshakeLoop = false; try { testOneCipher(cs); } catch (Throwable t) { error = t; } switch (ciphers) { case SUPPORTED_NON_KRB_CIPHERS: case SUPPORTED_NON_KRB_NON_SHA_CIPHERS: case SUPPORTED_KRB_CIPHERS: case ENABLED_NON_KRB_NOT_ANON_CIPHERS: if (error != null) { System.out.println("Test Failed: " + cs); System.err.println("Test Exception for " + cs); error.printStackTrace(); failedNum++; } else { System.out.println("Test Passed: " + cs); } break; case UNSUPPORTED_CIPHERS: if (error == null) { System.out.println("Test Failed: " + cs); System.err.println("Test for " + cs + " should have thrown " + "IllegalArgumentException, but it has not!"); failedNum++; } else if (!(error instanceof IllegalArgumentException)) { System.out.println("Test Failed: " + cs); System.err.println("Test Exception for " + cs); error.printStackTrace(); failedNum++; } else { System.out.println("Test Passed: " + cs); } break; default: throw new Error("Test issue: unexpected ciphers: " + ciphers.name()); } } return failedNum; } /** * Method used for the handshake routine. * * @param wrapingEngine - Engine that is expected to wrap data. * @param unwrapingEngine - Engine that is expected to unwrap data. * @param maxPacketSize - Maximum packet size for MFLN of zero * for no limit. * @param enableReplicatedPacks - Set {@code true} to enable replicated * packet sending. * @throws SSLException - thrown on engine errors. */ private static void handshakeProcess(SSLEngine wrapingEngine, SSLEngine unwrapingEngine, int maxPacketSize, boolean enableReplicatedPacks) throws SSLException { HandshakeStatus wrapingHSStatus = wrapingEngine.getHandshakeStatus(); HandshakeStatus unwrapingHSStatus = unwrapingEngine.getHandshakeStatus(); SSLEngineResult r; String wrapper, unwrapper; if (wrapingEngine.getUseClientMode() && !unwrapingEngine.getUseClientMode()) { wrapper = "Client"; unwrapper = "Server"; } else if (unwrapingEngine.getUseClientMode() && !wrapingEngine.getUseClientMode()) { wrapper = "Server"; unwrapper = "Client"; } else { throw new Error("Both engines are in the same mode"); } System.out.println( wrapper + " handshake (wrap) status " + wrapingHSStatus); System.out.println( unwrapper + " handshake (unwrap) status " + unwrapingHSStatus); ByteBuffer netReplicatedClient = null; ByteBuffer netReplicatedServer = null; switch (wrapingHSStatus) { case NEED_WRAP: if (enableReplicatedPacks) { if (net != null) { net.flip(); if (net.remaining() != 0) { if (wrapingEngine.getUseClientMode()) { netReplicatedServer = net; } else { netReplicatedClient = net; } } } } ByteBuffer app = ByteBuffer.allocate( wrapingEngine.getSession().getApplicationBufferSize()); net = doWrap(wrapingEngine, wrapper, maxPacketSize, app); wrapingHSStatus = wrapingEngine.getHandshakeStatus(); // No break, falling into unwrapping. case NOT_HANDSHAKING: switch (unwrapingHSStatus) { case NEED_TASK: runDelegatedTasks(unwrapingEngine); case NEED_UNWRAP: doUnWrap(unwrapingEngine, unwrapper, net); if (enableReplicatedPacks) { System.out.println(unwrapper + " unwrapping replicated packet..."); if (unwrapingEngine.getHandshakeStatus() .equals(HandshakeStatus.NEED_TASK)) { runDelegatedTasks(unwrapingEngine); } ByteBuffer netReplicated; if (unwrapingEngine.getUseClientMode()) { netReplicated = netReplicatedClient; } else { netReplicated = netReplicatedServer; } if (netReplicated != null) { doUnWrap(unwrapingEngine, unwrapper, netReplicated); } else { net.flip(); doUnWrap(unwrapingEngine, unwrapper, net); } } break; case NEED_UNWRAP_AGAIN: break; case NOT_HANDSHAKING: if (doUnwrapForNotHandshakingStatus) { System.out.println("Not handshake status unwrap"); doUnWrap(unwrapingEngine, unwrapper, net); doUnwrapForNotHandshakingStatus = false; break; } else { if (wrapingHSStatus == HandshakeStatus.NOT_HANDSHAKING) { System.out.println("Handshake is completed"); endHandshakeLoop = true; } } break; case NEED_WRAP: SSLSession session = unwrapingEngine.getSession(); int bufferSize = session.getApplicationBufferSize(); ByteBuffer b = ByteBuffer.allocate(bufferSize); net = doWrap(unwrapingEngine, unwrapper, maxPacketSize, b); unwrapingHSStatus = unwrapingEngine.getHandshakeStatus(); if ((wrapingHSStatus == HandshakeStatus.NOT_HANDSHAKING) && (unwrapingHSStatus == HandshakeStatus.NOT_HANDSHAKING)) { System.out.println("Handshake is completed"); endHandshakeLoop = true; } break; default: throw new Error( "Unexpected unwraping engine handshake status " + unwrapingHSStatus.name()); } break; case NEED_UNWRAP: break; case NEED_UNWRAP_AGAIN: net.flip(); doUnWrap(wrapingEngine, wrapper, net); break; case NEED_TASK: runDelegatedTasks(wrapingEngine); break; default: throw new Error("Unexpected wraping engine handshake status " + wrapingHSStatus.name()); } } private static void runDelegatedTasks(SSLEngine engine) { Runnable runnable; System.out.println("Running delegated tasks..."); while ((runnable = engine.getDelegatedTask()) != null) { runnable.run(); } HandshakeStatus hs = engine.getHandshakeStatus(); if (hs == HandshakeStatus.NEED_TASK) { throw new Error("Handshake shouldn't need additional tasks."); } } /** * Start a KDC server: * - create a KDC instance * - create Kerberos principals * - save Kerberos configuration * - save keys to keytab file * - no pre-auth is required */ private static void startKDC(String realm, Map principals, String ktab) { try { KDC kdc = KDC.create(realm, HOST, 0, true); kdc.setOption(KDC.Option.PREAUTH_REQUIRED, Boolean.FALSE); if (principals != null) { principals.entrySet().stream().forEach((entry) -> { String name = entry.getKey(); String password = entry.getValue(); if (password == null || password.isEmpty()) { System.out.println("KDC: add a principal '" + name + "' with a random password"); kdc.addPrincipalRandKey(name); } else { System.out.println("KDC: add a principal '" + name + "' with '" + password + "' password"); kdc.addPrincipal(name, password.toCharArray()); } }); } KDC.saveConfig(KRB5_CONF_FILENAME, kdc); if (ktab != null) { File ktabFile = new File(ktab); if (ktabFile.exists()) { System.out.println("KDC: append keys to an exising " + "keytab file " + ktab); kdc.appendKtab(ktab); } else { System.out.println("KDC: create a new keytab file " + ktab); kdc.writeKtab(ktab); } } System.out.println("KDC: started on " + HOST + ":" + kdc.getPort() + " with '" + realm + "' realm"); } catch (Exception e) { throw new RuntimeException("KDC: unexpected exception", e); } } }