/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.transport.tcp; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.net.SocketException; import java.net.URI; import java.net.UnknownHostException; import java.security.KeyStore; import junit.framework.Test; import junit.textui.TestRunner; import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.FakeTransportConnector; import org.apache.activemq.broker.SslBrokerService; import org.apache.activemq.broker.SslContext; import org.apache.activemq.broker.TransportConnector; import org.apache.activemq.transport.TransportBrokerTestSupport; import org.apache.activemq.transport.TransportFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SslBrokerServiceTest extends TransportBrokerTestSupport { private static final Logger LOG = LoggerFactory.getLogger(SslBrokerServiceTest.class); TransportConnector needClientAuthConnector; TransportConnector limitedCipherSuites; @Override protected String getBindLocation() { return "ssl://localhost:0"; } @Override protected BrokerService createBroker() throws Exception { // http://java.sun.com/javase/javaseforbusiness/docs/TLSReadme.html // work around: javax.net.ssl.SSLHandshakeException: renegotiation is not allowed System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true"); SslBrokerService service = new SslBrokerService(); service.setPersistent(false); String baseUri = getBindLocation(); String uri0 = baseUri + "?" + TransportConstants.SSL_ENABLED_PROP_NAME + "=true&" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "=" + SslTransportBrokerTest.SERVER_KEYSTORE + "&" + TransportConstants.KEYSTORE_PASSWORD_PROP_NAME + "=" + SslTransportBrokerTest.PASSWORD + "&" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "=" + SslTransportBrokerTest.KEYSTORE_TYPE; String uri1 = uri0 + "&" + TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME + "=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,SSL_DH_anon_WITH_3DES_EDE_CBC_SHA"; String uri2 = uri0 + "&" + TransportConstants.NEED_CLIENT_AUTH_PROP_NAME + "=true&" + TransportConstants.TRUSTSTORE_PATH_PROP_NAME + "=" + SslTransportBrokerTest.TRUST_KEYSTORE + "&" + TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME + "=" + SslTransportBrokerTest.PASSWORD + "&" + TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME + "=" + SslTransportBrokerTest.KEYSTORE_TYPE; //broker side TransportConnector serverConnector0 = service.addConnector(new URI(uri0)); connector = new FakeTransportConnector(new URI("ssl://localhost:" + serverConnector0.getUri().getPort())); TransportConnector serverConnector1 = service.addConnector(new URI(uri1)); limitedCipherSuites = new FakeTransportConnector(new URI("ssl://localhost:" + serverConnector1.getUri().getPort() + "?transport.enabledCipherSuites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,SSL_DH_anon_WITH_3DES_EDE_CBC_SHA")); TransportConnector serverConnector2 = service.addConnector(new URI(uri2)); needClientAuthConnector = new FakeTransportConnector(new URI("ssl://localhost:" + serverConnector2.getUri().getPort() + "?transport.needClientAuth=true")); KeyManager[] km = getKeyManager(); TrustManager[] tm = getTrustManager(); // for client side SslTransportFactory sslFactory = new SslTransportFactory(); SslContext ctx = new SslContext(km, tm, null); SslContext.setCurrentSslContext(ctx); TransportFactory.registerTransportFactory("ssl", sslFactory); return service; } public void testNeedClientAuthReject() throws Exception { SSLContext context = SSLContext.getInstance("TLS"); // no client cert context.init(null, getTrustManager(), null); try { makeSSLConnection(context, null, needClientAuthConnector); fail("expected failure on no client cert"); } catch (SSLException expected) { expected.printStackTrace(); } // should work with regular connector makeSSLConnection(context, null, connector); } public void testNeedClientAuthSucceed() throws Exception { SSLContext context = SSLContext.getInstance("TLS"); context.init(getKeyManager(), getTrustManager(), null); makeSSLConnection(context, null, needClientAuthConnector); } public void testCipherSuitesDisabled() throws Exception { SSLContext context = SSLContext.getInstance("TLS"); context.init(getKeyManager(), getTrustManager(), null); // Enable only one cipher suite which is not enabled on the server try { makeSSLConnection(context, new String[]{"SSL_RSA_WITH_RC4_128_MD5"}, limitedCipherSuites); fail("expected failure on non allowed cipher suite"); } catch (SSLException expectedOnNotAnAvailableSuite) { } // ok with the enabled one makeSSLConnection(context, new String[]{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}, limitedCipherSuites); } private void makeSSLConnection(SSLContext context, String enabledSuites[], TransportConnector connector) throws Exception, UnknownHostException, SocketException { SSLSocket sslSocket = (SSLSocket) context.getSocketFactory().createSocket("localhost", connector.getUri().getPort()); if (enabledSuites != null) { sslSocket.setEnabledCipherSuites(enabledSuites); } sslSocket.setSoTimeout(5000); SSLSession session = sslSocket.getSession(); sslSocket.startHandshake(); LOG.info("cyphersuite: " + session.getCipherSuite()); LOG.info("peer port: " + session.getPeerPort()); LOG.info("peer cert: " + session.getPeerCertificateChain()[0].toString()); } public static TrustManager[] getTrustManager() throws Exception { TrustManager[] trustStoreManagers = null; KeyStore trustedCertStore = KeyStore.getInstance(SslTransportBrokerTest.KEYSTORE_TYPE); trustedCertStore.load(new FileInputStream(SslTransportBrokerTest.TRUST_KEYSTORE), null); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustedCertStore); trustStoreManagers = tmf.getTrustManagers(); return trustStoreManagers; } public static KeyManager[] getKeyManager() throws Exception { KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); KeyStore ks = KeyStore.getInstance(SslTransportBrokerTest.KEYSTORE_TYPE); KeyManager[] keystoreManagers = null; byte[] sslCert = loadClientCredential(SslTransportBrokerTest.SERVER_KEYSTORE); if (sslCert != null && sslCert.length > 0) { ByteArrayInputStream bin = new ByteArrayInputStream(sslCert); ks.load(bin, SslTransportBrokerTest.PASSWORD.toCharArray()); kmf.init(ks, SslTransportBrokerTest.PASSWORD.toCharArray()); keystoreManagers = kmf.getKeyManagers(); } return keystoreManagers; } private static byte[] loadClientCredential(String fileName) throws IOException { if (fileName == null) { return null; } FileInputStream in = new FileInputStream(fileName); ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buf = new byte[512]; int i = in.read(buf); while (i > 0) { out.write(buf, 0, i); i = in.read(buf); } in.close(); return out.toByteArray(); } @Override protected void setUp() throws Exception { maxWait = 10000; super.setUp(); } public static Test suite() { return suite(SslBrokerServiceTest.class); } public static void main(String[] args) { TestRunner.run(suite()); } }